blockdiag-1.5.3/0000755000076600000240000000000012556430040014237 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/.hgignore0000644000076600000240000000022612227702770016051 0ustar tkomiyastaff00000000000000.pyc .swp ^.installed.cfg ^.coverage ^.tox ^bin/ ^build/ ^_build/ ^develop-eggs/ ^dist/ ^eggs/ ^parts/ ^src/.*.egg-info ^src/blockdiag/tests/truetype blockdiag-1.5.3/.hgtags0000644000076600000240000001204612526026536015530 0ustar tkomiyastaff00000000000000f13b2f2d914399eae8bf7b4b5105a69eb7e657ca 0.1 af0e42d164178abd7f849b2d5659fe01b7eed69c 0.2 211617855c50a76c0226ecb83d659ba77323c0d9 0.2.2 0e0308059b7d67b2d7c2bd0a3e6989dcce6c7129 0.3 183f989ca4bb9e6ac44dacf588312f77e4b715ea 0.3.1 c799939494fcf8adefeb4e129c111af8253519c3 0.4 3de87d6c2c78240db3b3f800f551169e3360c16a 0.4.1 7070f6188d7278ef26ca4e6f2fa1a1faac8853c3 0.2.1 6dfea7499697f839240ab49ffad1000e34bcf834 0.4.2 7a86de04a5247bc775a956e252949c04753b1449 0.5 c86ba6300a6f00cb490f952c321c7596ba54dfaa 0.5.1 b73c44fbe87dd5c478dbe5c861d6833381022865 0.5.2 200c7a578e3c98db5876b4db225ec3a69cb28b5b 0.5.3 45835d84017e5da4d7618d2eaaab98ba9d575572 0.5.4 92e1ffe35380687329273214b2f4676a5c960ed8 0.5.5 d6af195862867049b81021ae359668f405fdb4f5 0.6 52ed26c213248b738ddb1b8c7f97288f087c330a 0.6.1 52ed26c213248b738ddb1b8c7f97288f087c330a 0.6.1 0000000000000000000000000000000000000000 0.6.1 0000000000000000000000000000000000000000 0.6.1 63bc462fbc487ed1f1c55c2ab9e101bd97f87d99 0.6.1 6061c7f1706c1164cc34da48c19e543c56be007c 0.6.2 84e23355450870f51a35e2931ef80f31f1f130fb 0.6.3 87382e9a446bc06adbb57c52530ebcee10749738 0.6.4 4634b17e273837c2a2d6a8effaf7fd0343e226cf 0.6.5 f2d6cbbd62619c556afe40fa17f7da314e3d66a0 0.6.6 663a52ef768c7725984db74fd9064fd2bca0263c 0.6.7 01ccc2e285e57e3df3656d21eb86ea3e2c4de6e7 0.7.0 123ed155fd4e933dfb5182af6a4bb67b776f06e3 0.7.1 2d6ef5898a4c8c127e3ca256263597dbef0192ec 0.7.2 93e52fb89351e2b40b2058829a41157d72a6b4fe 0.7.3 afa7e91236bebf58d9c2081b328a8f6a209158a5 0.7.4 dc056c5edf2fdb73c68ce55957e36442cf259bda 0.7.5 9c9bc6e859f7e1bf2cbdf296cb98bdd43e177339 0.7.6 f14667d477b861e146ca82974852591f343955b4 0.7.7 6c6edf112349b3dcd525ecf805cf2d1ddce95923 0.7.8 56d8d6c04be5c495d52479cd7273054652ea0f41 0.8.0 5317b0bcec53c4f83badc21675255fa4f810c51b 0.8.1 5317b0bcec53c4f83badc21675255fa4f810c51b 0.8.1 0000000000000000000000000000000000000000 0.8.1 0000000000000000000000000000000000000000 0.8.1 4e291393b9818f9b744599753213b2f214418367 0.8.1 1394f7cb6d74256841d52b65559a9181f38dc3c0 0.8.2 1394f7cb6d74256841d52b65559a9181f38dc3c0 0.8.2 0000000000000000000000000000000000000000 0.8.2 0000000000000000000000000000000000000000 0.8.2 a39c2fe87c56ecd3330fa966c16a84a719c078af 0.8.2 dee693b247f9796ef5a71c9879e7ece81e2ca826 0.8.3 e26b9e4dea0da3d3decaee7524add158cd8f9a89 0.8.4 3ee05b403cb89fccf29f8225238119322d574343 0.8.5 66503507bf9021e9373a26963380c8daefe8b732 0.8.6 58c589bf671872eb14cb8a97572cd54f4451363d 0.8.7 a5ae7cf75f7932c938e66df154f766d530ec9a10 0.8.8 aeb61e8fad968a0a252e7be969e558b8058087ac 0.8.9 9547a2a4d61e3d136913ba2fd281f568b06c26f1 0.9.0 b8fcad4288f703ce4fe68c0f66cde6f6cfda14ea 0.9.1 4100c1b16943477421d72dbcc47f17636cf3cd07 0.9.2 71e676fb5eb2287ac8d864be6e3f6a1ce404e66e 0.9.3 f0c827676fda31eec4570b901bc9a4e8094f1f6e 0.9.5 f0c827676fda31eec4570b901bc9a4e8094f1f6e 0.9.5 0000000000000000000000000000000000000000 0.9.5 0000000000000000000000000000000000000000 0.9.5 9c07fdfc24682a8a935c139113f20c60a1ded6cd 0.9.5 9c07fdfc24682a8a935c139113f20c60a1ded6cd 0.9.5 0000000000000000000000000000000000000000 0.9.5 0000000000000000000000000000000000000000 0.9.5 989c2c9e33af4c1d7033738a0e3346e2d1fe78aa 0.9.5 a110a4a98ad14ed551539d2ce9bf022e7a65099c 0.9.6 00957a44f847d8582b6be748ae47e613be57c5b6 0.9.7 91f704283be06c84cfbe614be97cd43c3f342845 1.0.1 1d6039743bd6f7056293432556cea030fbf921fc 1.0.2 e7a0149a88f05fc2064d37f948252397a8cd945f 1.0.0 84d6892aa674d64808d30139617550a96c50054a 1.0.3 e180448fb931f2cc5f0c3876b7e9fe050ffdcb74 1.1.0 bbc158edfa84fc78e694d19a7fd570127e098a82 1.1.1 f7fc3127b629accae4b82ec69d7b13016b7093b5 1.1.2 28eb38df230448b99bf11909f30232f9028e44c3 1.1.3 28eb38df230448b99bf11909f30232f9028e44c3 1.1.3 0000000000000000000000000000000000000000 1.1.3 0000000000000000000000000000000000000000 1.1.3 caa5f3cae7574d94efd86da6a7cba5e614ef905e 1.1.3 dc9debc39155dad8e6bb18ead11d9af876170bc6 1.1.4 f2faaa5683f7cf3e3ffb8927ad2a9a11ef9b62e5 1.1.5 b57b099723600a0eb289220ffadc6b52ab3aca55 1.1.6 ddff661e8fd9fab186382c5629032d918bfe5b48 1.1.7 0c6e79e5491ca681ab1eb74c8b6bf84b9f7db114 1.1.8 2a028e8248876b143d02e4113cf0e6aa45ec49da 1.2.0 5f30defbfa6425144d9f41be9dae4e6fdd5cb1f8 1.2.1 a58111e3769fbefea485fe78c136b1bf083566dd 1.2.2 a58111e3769fbefea485fe78c136b1bf083566dd 1.2.2 0000000000000000000000000000000000000000 1.2.2 0000000000000000000000000000000000000000 1.2.2 1e7de1d0e4656230f0b367f4a76e80e40f20747f 1.2.2 b7204023d673853f16687412f0758d66f669fe6e 1.2.3 924f66da959d9e6ac1f4831cf8a9fbb18a57fb8a 1.2.4 eea92cc34dd8702eb368fb274de87b4b9bfde8c9 1.3.0 5181da3c729559101982dee60cf2bac4098913b7 1.3.1 eaeb1c38be3dfc0ae6e996b38437e8bdf9799ea2 1.3.2 372e5f369ba92ed5efa40139393dea58cdf475d9 1.3.3 4746d577dcfbe02ba535d70912236b10723a52f8 1.4.0 41e73287a1d6e019dd1f6940d184f05adcd07b33 1.4.1 eda0e6b41fdf9939d0d160fbdf2dfce5e7f3135e 1.4.2 b7481dd07fe9b8f815eb6989c53b2ab7dc490f50 1.4.3 fdf8d4f28170b81b237afdb787b81c13a0d8827a 1.4.4 dc4a431c445e57411e8800577afa761ee4e476f0 1.4.5 8b62ba06c865d32b6c02e185fb4ebe6478322d41 1.4.6 bb9af150c55e8497c75f93250774d64dc7798bd9 1.4.7 b67e529c41e7fac9796c6635e7a5b801f98a4de7 1.5.0 1f35ac7a1e98fcdf8c74164ce721d5ab725f003a 1.5.1 90c65044ba04b5a305980f062c83a481bf7253cf 1.5.2 blockdiag-1.5.3/.installed.cfg0000644000076600000240000000516212534756327017000 0ustar tkomiyastaff00000000000000[buildout] installed_develop_eggs = /Users/tkomiya/work/blockdiag/develop-eggs/blockdiag.egg-link parts = blockdiag test static_analysis [blockdiag] __buildout_installed__ = /Users/tkomiya/work/blockdiag/bin/blockdiag /Users/tkomiya/work/blockdiag/bin/py __buildout_signature__ = zc.recipe.egg-2.0.1-py2.7.egg setuptools-e254772190d6726b0025d350d2b623f9 zc.buildout-2.3.1-py2.7.egg _b = /Users/tkomiya/work/blockdiag/bin _d = /Users/tkomiya/work/blockdiag/develop-eggs _e = /Users/tkomiya/.buildout/buildout-eggs bin-directory = /Users/tkomiya/work/blockdiag/bin develop-eggs-directory = /Users/tkomiya/work/blockdiag/develop-eggs eggs = blockdiag[rst] eggs-directory = /Users/tkomiya/.buildout/buildout-eggs interpreter = py recipe = zc.recipe.egg [test] __buildout_installed__ = /Users/tkomiya/work/blockdiag/bin/test __buildout_signature__ = pbp.recipe.noserunner-0.2.6-py2.7.egg zc.recipe.egg-2.0.1-py2.7.egg nose-1.3.4-py2.7.egg zc.buildout-2.3.1-py2.7.egg setuptools-e254772190d6726b0025d350d2b623f9 _b = /Users/tkomiya/work/blockdiag/bin _d = /Users/tkomiya/work/blockdiag/develop-eggs _e = /Users/tkomiya/.buildout/buildout-eggs bin-directory = /Users/tkomiya/work/blockdiag/bin develop-eggs-directory = /Users/tkomiya/work/blockdiag/develop-eggs eggs = blockdiag[rst] blockdiag[testing] coverage unittest-xml-reporting nose eggs-directory = /Users/tkomiya/.buildout/buildout-eggs location = /Users/tkomiya/work/blockdiag/parts/test recipe = pbp.recipe.noserunner script = /Users/tkomiya/work/blockdiag/bin/test [static_analysis] __buildout_installed__ = /Users/tkomiya/work/blockdiag/bin/coverage2 /Users/tkomiya/work/blockdiag/bin/coverage-2.7 /Users/tkomiya/work/blockdiag/bin/coverage /Users/tkomiya/work/blockdiag/bin/flake8 /Users/tkomiya/work/blockdiag/bin/pyreverse /Users/tkomiya/work/blockdiag/bin/pylint /Users/tkomiya/work/blockdiag/bin/epylint /Users/tkomiya/work/blockdiag/bin/pylint-gui /Users/tkomiya/work/blockdiag/bin/symilar /Users/tkomiya/work/blockdiag/bin/epylint /Users/tkomiya/work/blockdiag/bin/pylint /Users/tkomiya/work/blockdiag/bin/pylint-gui /Users/tkomiya/work/blockdiag/bin/pyreverse /Users/tkomiya/work/blockdiag/bin/symilar __buildout_signature__ = zc.recipe.egg-2.0.1-py2.7.egg setuptools-e254772190d6726b0025d350d2b623f9 zc.buildout-2.3.1-py2.7.egg _b = /Users/tkomiya/work/blockdiag/bin _d = /Users/tkomiya/work/blockdiag/develop-eggs _e = /Users/tkomiya/.buildout/buildout-eggs bin-directory = /Users/tkomiya/work/blockdiag/bin develop-eggs-directory = /Users/tkomiya/work/blockdiag/develop-eggs eggs = coverage flake8 pylint eggs-directory = /Users/tkomiya/.buildout/buildout-eggs recipe = zc.recipe.egg blockdiag-1.5.3/a.diag0000644000076600000240000000016212555655374015326 0ustar tkomiyastaff00000000000000{ A -> B; C [width = 200, shape = roundedbox]; A [label = "hello\n\nwolrd hello world hello world"]; A; } blockdiag-1.5.3/b.diag0000644000076600000240000000012612366147067015322 0ustar tkomiyastaff00000000000000{ A -> B -> C; D -> E; A, B [shape = circle]; E [shape = square]; pgqywz } blockdiag-1.5.3/b.png0000644000076600000240000001714212366150727015205 0ustar tkomiyastaff00000000000000PNG  IHDRG)IDATxݻ񧪫o i # X6Ā"9R +AVn) 3e8RÐ BܹܺOu6GC.3gNw 8!gS~jWhC 2@"C  2@"C  2@"C  2@"C  2@"C  2@"C  2@"C  2@"C  2@ȘCRʽsqIaK{}؟m+;m nG6[|sx&C pbw-_[DDڶM}Cc!N>'[mkR^q`oiߗ89XkS! J)r`XRmD|_cvmj+ pݹ%‚p&,kڶ59;rL]mk8]EsGjHK{|ۛ0 bC]׹6m6響>@6.EHL ?Cl}zic9T SZksLw~&.//VE]nzUɏ7BC{QJI]ׅl1""Z!{}ƒw~~ֈh]$V)ԕa uIc&ATbĝ0qBDD14MkcL$Iwꄝ@a x$:ϻ~cNuO Euw8@r<|i$*!*b9E$<1Hp֣}T*HT0[} rEWUUu Mۻ0qWc>xHaz^u]vM߇ 7G?=`5`ĔRrz6ͬiq:9Q0C&@Mih.^K]-`U=6aKqhݞ]ö27߇??c2I3)Mp(7_C$.^aoÇ~O|?~b(bm{Ӓc<CMSoXs `[J<^}d ?,9ݶmr `dP}. [` N zPA >_n~}m^hTza'ܕm>&pι{ͥn#NM7ǭnǘpp?+Wd oA^;{w:rh0 pzK^,RNkjn_//]ar<bp?& ?II_[SJЇcN)֣|~e$Ktg ࡏ Oc4i6i$7aˇ%0/s&~4M<7yo<ߦiZcloLIc-T@$I_9*RJ>e٦(u竢(Vyo4m HKo{B!"c,ۆ Q8,<7eY,Wyo,ۆ 1H/%$Ile͟x>e٦,UYlb6-˲\gYoA6ߟ4|b8痳EVo{BDIƘ]|oRQJÿdY.b9^,κIp&lr&?p -rYUwȉ$|'OceuQ˪.ΎUUuY21fG^MCpzͲ.rUU3}L""ɓb8::UU]eʲ-0`1jݔRahO;!v 2_>~ޟ}c MY/:_,_<;==}zzz|>U?` o}ٛ{ziQ4>jd[eb5.֦oٳg*>p8a>裿i-{<{ї?ѣG_X,ʲ\a>zgϞ}>ӧOsvvryRui۶/1g-rx:MUU秧_c?j[.8o)"RJ^)%Z6Mӝsn{Ѷqi\W/ӟ0pㅪ;>>*T?4w}Ɠr(\\fٲiBD$MZ: oۻۢi.ҺJ`Z{i,k^uW_^^>RJ=y9?裿ue]MɳǏѯ~TUuQEɄa㾅ZKE]ׅHeۦi km{:52J뺟v6-~Z *pdRX1xU B?/zl_m~jv/Meul68::: =O=2T>y8{-mXWUur<ϷfnB Ƙ&UU]E641=8"7Œ1""X1fD_ŋju]6MSZkӶmS-WcR.I]wŴ?jX=?>>~~zzP}X½`yu%6eYFjٷPk6xu {. m1ƆL<G&LZkI_It5\xdTfng. m&enkClMVeY.yUUgGGGgϏ|>-{-/nɎKӴ)r%"bEQɭ&TX?UmubU*MFGIF?Ͳl[Ŧ.z^VE]nLxU\Q>LJJ|b6$xYUEeNU$\8iڈ\-iZfe FpG;ki1 m#OJдMf|~^:T@u].vi۶s. _4MwE wu,eXʲl鮷 |(K7%"k^'t7ujm`* LUwGr%ZcM...7,&WH* uۮ )r&z00qP}_!\Nh_ދOtMdM.U?7@ mJaO|\5p`0vcHjnawBD$G'䖫Y$gڶ59kpVrbFpg+_t΍/& w>?[(//a0TG&/V{kɃa>8Y7&LqG"T8O4` 1rKK)>[> 02︟L!"0!6!DgD @d!D @d!D @d!D @d!DsQJC]y08@?!3x1 WG9UUmkRNkR8zp7ha΍16IVk֮ ]0z@qn,*˲&M&MFDDkDDzAxP@D9:{Ϥ<S(|T,˕RVJx;!ѣGkRȲZJ)ے!M.gL tr,{C3 Am}C#;"#tBrǘ;"C  2@"C  2@"C  2@QJw!@DNDRʉ(I3>!U$Il$)ɋ6з^z~qqٳg|Ͼ}k\u]Zkm'#E?'Ib1u盪OOO44Ƙ]$Vk݊!ER!yZ$IZcͲl[j6-)DD4sK)%ZkkylY*˲1&TC$^c `h AXZƘ]euY|~Qu!"eٶiZz^NR*,gfˣ 1fnR@)!: `kiXWUur<Ϸfnڶ59i] ܄rr򼪪ˢ(Ƙ&@Z3cWW\۔eeߪ.BmDD4K -rUUUUwUcCP1]zLQPi6eYDD1(u4քK㧔r*Ͳ.b],U͍>@weΝsK=2]zL˛_7]LZ0s9۶Mf=AhmBF/nAM) i/^'2]zL (om: b {! "D @d!D @d!D @d!D @d!D @d!DM)} 8,=BDED>RJJD|9TWT]`Ҩ"zmZ{T({u[kӶm?dE]d""ʲIӴIӴZ;^EɈ6rGeYʲ\(jWJy=7&'(BW߳_$V)sTT!"rvv1ƦiZc$IZSW? `Rb"4CDiZEfK\һjulM4@` t[V0ADE64ιDzOa(mۚ&< "GP@\!D @db\=>u7?>~s_u_}>.7{o |?0L,7euAu _n z?ƅ8Q.827w3`D @d!D @d!D @d!" Ci8QJf}CЫ BI$IZSJm o^g>Ͽg}ӧ9;;r<뺴m^yOF~Oc<7UU~ii1$IֺB )2*BJ)Ieٶ,l6[6MSiZ[kS!RJ|3/fٲ,Ue[cLHLR0}Z˲.r5/.DD,6MSXkSދɑQJ]lytt|>_t!6֭RSuEr""yo7ZmksZn ayUUEQ1M9zuuKMY+kZ"TڶMDD<^]Eu_UeUU]1P1B `bKӴ)r%"bEQɭ&TX?UmeuQ,WeY4mn09p#H]al6[ʟs.Uh :mc,˚4M4Mn;nLVP冿FCs.ޔ(ޓFËZ{u^ @TEjusΩn_;q~{sa7D"tCDoDX| TSGy]wN-=z ~ t&AHSþv!D @d!D @d!D @d!D @d!D @d!D @d!Dt ^ٸIENDB`blockdiag-1.5.3/blockdiag.10000644000076600000240000000441611716343703016253 0ustar tkomiyastaff00000000000000.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH BLOCKDIAG 1 "May 9, 2011" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME blockdiag \- generate block-diagram image file from spec-text file. .SH SYNOPSIS .B blockdiag .RI [ options ] " file" .SH DESCRIPTION This manual page documents briefly the .B blockdiag commands. .PP .\" TeX users may be more comfortable with the \fB\fP and .\" \fI\fP escape sequences to invode bold face and italics, .\" respectively. \fBblockdiag\fP is a program that generate block-diagram image file from spec-text file. .SH OPTIONS These programs follow the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. For a complete description, see the \fBSEE ALSO\fP. .TP .B \-\-version show program's version number and exit .TP .B \-h, \-\-help Show summary of options .TP .B \-a, \-\-antialias Pass diagram image to anti-alias filter .TP .B \-c FILE, \-\-config=FILE read configurations from FILE .TP .B \-o FILE write diagram to FILE .TP .B \-f FONT, \-\-font=FONT use FONT to draw diagram .TP .B \-\-fontmap=FONT use FONTMAP file to draw diagram .TP .B \-s, \-\-separate Separate diagram images for each group .TP .B \-T TYPE Output diagram as TYPE format .TP .B \-\-nodoctype Do not output doctype definition tags (SVG only) .TP .B \-\-no-transparency do not make transparent background of diagram (PNG only) .SH SEE ALSO The programs are documented fully by .br .BR http://tk0miya.bitbucket.org/blockdiag/build/html/index.html .SH AUTHOR blockdiag was written by Takeshi Komiya .PP This manual page was written by Kouhei Maeda , for the Debian project (and may be used by others). blockdiag-1.5.3/blockdiag.svg0000644000076600000240000000752612556277731016731 0ustar tkomiyastaff00000000000000 blockdiag blockdiag { default_node_color = lightyellow; default_group_color = red; default_linecolor = blue; default_textcolor = black; A[href="http://google.com"]; A[color = "lightgreen"]; A[numbered = 007]; A -> B -> C; B -> D; group { A;C; } } B A 007 C D blockdiag-1.5.3/bootstrap.py0000644000076600000240000001306612427536665016655 0ustar tkomiyastaff00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os import shutil import sys import tempfile from optparse import OptionParser tmpeggs = tempfile.mkdtemp() usage = '''\ [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --find-links to point to local resources, you can keep this script from going over the network. ''' parser = OptionParser(usage=usage) parser.add_option("-v", "--version", help="use a specific zc.buildout version") parser.add_option("-t", "--accept-buildout-test-releases", dest='accept_buildout_test_releases', action="store_true", default=False, help=("Normally, if you do not specify a --version, the " "bootstrap script and buildout gets the newest " "*final* versions of zc.buildout and its recipes and " "extensions for you. If you use this flag, " "bootstrap and buildout will get the newest releases " "even if they are alphas or betas.")) parser.add_option("-c", "--config-file", help=("Specify the path to the buildout configuration " "file to be used.")) parser.add_option("-f", "--find-links", help=("Specify a URL to search for buildout releases")) options, args = parser.parse_args() ###################################################################### # load/install setuptools to_reload = False try: import pkg_resources import setuptools except ImportError: ez = {} try: from urllib.request import urlopen except ImportError: from urllib2 import urlopen # XXX use a more permanent ez_setup.py URL when available. exec(urlopen('https://bitbucket.org/pypa/setuptools/raw/0.7.2/ez_setup.py' ).read(), ez) setup_args = dict(to_dir=tmpeggs, download_delay=0) ez['use_setuptools'](**setup_args) if to_reload: reload(pkg_resources) import pkg_resources # This does not (always?) update the default working set. We will # do it. for path in sys.path: if path not in pkg_resources.working_set.entries: pkg_resources.working_set.add_entry(path) ###################################################################### # Install buildout ws = pkg_resources.working_set cmd = [sys.executable, '-c', 'from setuptools.command.easy_install import main; main()', '-mZqNxd', tmpeggs] find_links = os.environ.get( 'bootstrap-testing-find-links', options.find_links or ('http://downloads.buildout.org/' if options.accept_buildout_test_releases else None) ) if find_links: cmd.extend(['-f', find_links]) setuptools_path = ws.find( pkg_resources.Requirement.parse('setuptools')).location requirement = 'zc.buildout' version = options.version if version is None and not options.accept_buildout_test_releases: # Figure out the most recent final version of zc.buildout. import setuptools.package_index _final_parts = '*final-', '*final' def _final_version(parsed_version): for part in parsed_version: if (part[:1] == '*') and (part not in _final_parts): return False return True index = setuptools.package_index.PackageIndex( search_path=[setuptools_path]) if find_links: index.add_find_links((find_links,)) req = pkg_resources.Requirement.parse(requirement) if index.obtain(req) is not None: best = [] bestv = None for dist in index[req.project_name]: distv = dist.parsed_version if _final_version(distv): if bestv is None or distv > bestv: best = [dist] bestv = distv elif distv == bestv: best.append(dist) if best: best.sort() version = best[-1].version if version: requirement = '=='.join((requirement, version)) cmd.append(requirement) import subprocess if subprocess.call(cmd, env=dict(os.environ, PYTHONPATH=setuptools_path)) != 0: raise Exception( "Failed to execute command:\n%s", repr(cmd)[1:-1]) ###################################################################### # Import and run buildout ws.add_entry(tmpeggs) ws.require(requirement) import zc.buildout.buildout if not [a for a in args if '=' not in a]: args.append('bootstrap') # if -c was provided, we push it back into args for buildout' main function if options.config_file is not None: args[0:0] = ['-c', options.config_file] zc.buildout.buildout.main(args) shutil.rmtree(tmpeggs) blockdiag-1.5.3/bootstrap.pyc0000644000076600000240000001047212427535440017004 0ustar tkomiyastaff00000000000000 z]8Rc @sdZddlZddlZddlZddlZddlmZejZdZ ede Z e j dddd e j d d d d ddde dde j dddde j dddde j \ZZe ZyddlZddlZWnek riZyddlmZWn!ek rRddlmZnXedjefdUededdZedeereenddlZx:ejD]+ZeejjkrejjeqqWnXejZ ej!dddegZ"ej#j$d ej%p4ej&r1d!ndZ%e%rVe"j(de%gne j)ej*j+d"j,Z-d#Z.ej/Z/e/dkrej& rddl0Zd/Z1d&Z2ej3j4d'e-gZ5e%re5j6e%fnej*j+e.Z7e5j8e7dk rgZ9dZ:xse5e7j;D]dZ<e<j=Z>e2e>r&e:dksYe>e:krke<gZ9e>Z:qe>e:kre9j?e<qq&q&We9re9j@e9dj/Z/qqne/rd(jAe.e/fZ.ne"j?e.ddlBZBeBjCe"d)eej#d*e-dkr4eDd+eEe"d,d!ne jee jFe.ddlGZHgeD]ZId-eIkraeI^qasej?d.nejJdk rdejJgedd+neHjKjKjLeejMedS(0sBootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. iN(t OptionParsersF[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] Bootstraps a buildout-based project. Simply run this script in a directory containing a buildout.cfg, using the Python that you want bin/buildout to use. Note that by using --find-links to point to local resources, you can keep this script from going over the network. tusages-vs --versionthelps"use a specific zc.buildout versions-ts--accept-buildout-test-releasestdesttaccept_buildout_test_releasestactiont store_truetdefaultsNormally, if you do not specify a --version, the bootstrap script and buildout gets the newest *final* versions of zc.buildout and its recipes and extensions for you. If you use this flag, bootstrap and buildout will get the newest releases even if they are alphas or betas.s-cs --config-files?Specify the path to the buildout configuration file to be used.s-fs --find-linkss-Specify a URL to search for buildout releases(turlopens;https://bitbucket.org/pypa/setuptools/raw/0.7.2/ez_setup.pytto_dirtdownload_delayituse_setuptoolss8from setuptools.command.easy_install import main; main()s-mZqNxdsbootstrap-testing-find-linksshttp://downloads.buildout.org/t setuptoolss zc.buildouts*final-s*finalcCs5x.|D]&}|d dkr|tkrtSqWtS(Nit*(t _final_partstFalsetTrue(tparsed_versiontpart((s*/Users/tkomiya/work/blockdiag/bootstrap.pyt_final_versionys t search_paths==tenvt PYTHONPATHsFailed to execute command: %sit=t bootstrap(s*final-s*final(Nt__doc__tostshutiltsysttempfiletoptparseRtmkdtempttmpeggsRtparsert add_optionRt parse_argstoptionstargst to_reloadt pkg_resourcesR t ImportErrortezturllib.requestRturllib2treadtdictt setup_argstreloadtpatht working_settentriest add_entrytwst executabletcmdtenvirontgett find_linksRtNonetextendtfindt Requirementtparsetlocationtsetuptools_patht requirementtversiontsetuptools.package_indexRRt package_indext PackageIndextindextadd_find_linkstreqtobtaintbesttbestvt project_nametdistRtdistvtappendtsorttjoint subprocesstcallt Exceptiontreprtrequiretzc.buildout.buildouttzctat config_filetbuildouttmaintrmtree(((s*/Users/tkomiya/work/blockdiag/bootstrap.pyts                              *   %blockdiag-1.5.3/buildout.cfg0000644000076600000240000000176012360234063016553 0ustar tkomiyastaff00000000000000[buildout] parts = blockdiag test static_analysis develop = . [blockdiag] recipe = zc.recipe.egg eggs = blockdiag[rst] interpreter = py [test] recipe = pbp.recipe.noserunner eggs = blockdiag[rst] blockdiag[testing] coverage unittest-xml-reporting [static_analysis] recipe = zc.recipe.egg eggs = coverage flake8 pylint [test-extra] recipe = iw.recipe.cmd:py on_install = true cmds = >>> url = "http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Fvlgothic%2F46966%2FVLGothic-20100416.zip" >>> buildout_dir = buildout.get('directory', '.') >>> path = os.path.join(buildout_dir, 'src/blockdiag/tests/truetype') >>> if not os.path.exists(path): ... os.makedirs(path) ... import cStringIO, urllib2, zipfile ... archive = urllib2.urlopen(url).read() ... zip = zipfile.ZipFile(cStringIO.StringIO(archive)) ... ttf = zip.read('VLGothic/VL-PGothic-Regular.ttf') ... open(os.path.join(path, 'VL-PGothic-Regular.ttf'), 'wb').write(ttf) blockdiag-1.5.3/c.diag0000644000076600000240000000005011723275710015310 0ustar tkomiyastaff00000000000000{ A -> B -> C -> D, E -> F, G -> C; } blockdiag-1.5.3/CHANGES.rst0000644000076600000240000002423112556430004016043 0ustar tkomiyastaff00000000000000Changelog ========= 1.5.3 (2015-07-30) ------------------ * Fix bug - Fix #67 Group overlaps with nodes having href 1.5.2 (2015-05-17) ------------------ * Fix dependency; webcolors-1.5 does not support py32 * Fix bug - Fix images.open() failed with PIL 1.5.1 (2015-02-21) ------------------ * Fix bug - Fix labels are overwrapped on antialias mode 1.5.0 (2015-01-01) ------------------ * Refactor cleanup procedures * Fix bugs - Fix get_image_size() closes Image object automatically - Fix pasting Image object failed on SVG mode - Fix #61 images.urlopen() got PermissionError on Windows - Fix #61 images.open() raises exception if ghostscript not found - Fix #62 Use "sans-serif" to font-family property on SVG output ("sansserif" is invalid) - Fix #63 AttributeError on get_image_size(); Pillow (<= 2.4.x) does not have Image#close() 1.4.7 (2014-10-21) ------------------ * Fix bugs - Fix RuntimeError on unloading plugins 1.4.6 (2014-10-14) ------------------ * Show warnings on loading plugin multiple times * Unload all plugins on shutdown * Fix bugs - Fix caption is wrapped by paragraph node in reST parser 1.4.5 (2014-10-04) ------------------ * Add node event: build_finished * Take config object to plugins * Fix bugs - Fix utils.images.get_image_size() does not close an image descriptor 1.4.4 (2014-09-20) ------------------ * :caption: option of blockdiag directive recognizes inline markups * Fix bugs - Fix #58 failed to handle diagram definitions from stdin in py3 1.4.3 (2014-07-30) ------------------ * Show warnings on loading imagedrawers in debug mode * ImageDraw#image() accepts Image objects * Fix bugs - PNG: could not load png imagedrawer if could not access PIL.PILLOW_VERSION 1.4.2 (2014-07-12) ------------------ * SVG: Adjust text alignment precisely * Add plugin events: node.changing and cleanup * ImageDraw#image() accepts image from IO objects * Fix bugs - PDF: Fix failure text rotating - PDF: Fix failure pasting PNG images (256 palette/transparency) - PNG: Fix background of node was transparent on pasting transparent images 1.4.1 (2014-07-02) ------------------ * Change interface of docutils node (for sphinxcontrib module) * Fix bugs 1.4.0 (2014-06-23) ------------------ * Support embedding SVG/EPS images as background * Use wand to paste background images that is not supported by Pillow (if installed) * Add options to blockdiag directive (docutils extension) - \:width: - \:height: - \:scale: - \:align: - \:name: - \:class: - \:figwidth: - \:figclass: * actor shape supports label rendering 1.3.3 (2014-04-26) ------------------ * Add diagram attribute: default_node_style * Fix bugs 1.3.2 (2013-11-19) ------------------ * Fix bugs 1.3.1 (2013-10-22) ------------------ * Fix bugs 1.3.0 (2013-10-05) ------------------ * Support python 3.2 and 3.3 (thanks to @masayuko) * Drop supports for python 2.4 and 2.5 * Replace dependency: PIL -> Pillow 1.2.4 (2012-11-21) ------------------ * Fix bugs 1.2.3 (2012-11-05) ------------------ * Fix bugs 1.2.2 (2012-10-28) ------------------ * Fix bugs 1.2.1 (2012-10-28) ------------------ * Add external imagedraw plugin supports * Add node attribute: label_orientation* * Fix bugs 1.2.0 (2012-10-22) ------------------ * Optimize algorithm for rendering shadow * Add options to docutils directive * Fix bugs 1.1.8 (2012-09-28) ------------------ * Add --ignore-pil option * Fix bugs 1.1.7 (2012-09-20) ------------------ * Add diagram attribute: shadow_style * Add font path for centos 6.2 * Add a setting 'antialias' in the configuration file * Fix bugs 1.1.6 (2012-06-06) ------------------ * Support for readthedocs.org * reST directive supports :caption: option * Fix bugs 1.1.5 (2012-04-22) ------------------ * Embed source code to SVG document as description * Fix bugs 1.1.4 (2012-03-15) ------------------ * Add new edge.hstyles: oneone, onemany, manyone, manymany * Add edge attribute: description (for build description-tables) * Fix bugs 1.1.3 (2012-02-13) ------------------ * Add new edge type for data-models (thanks to David Lang) * Add --no-transparency option * Fix bugs 1.1.2 (2011-12-26) ------------------ * Support font-index for TrueType Font Collections (.ttc file) * Allow to use reST syntax in descriptions of nodes * Fix bugs 1.1.1 (2011-11-27) ------------------ * Add node attribute: href (thanks to @r_rudi!) * Fix bugs 1.1.0 (2011-11-19) ------------------ * Add shape: square and circle * Add fontfamily attribute for switching fontface * Fix bugs 1.0.3 (2011-11-13) ------------------ * Add plugin: attributes * Change plugin syntax; (cf. plugin attributes [attr = value, attr, value]) * Fix bugs 1.0.2 (2011-11-07) ------------------ * Fix bugs 1.0.1 (2011-11-06) ------------------ * Add group attribute: shape * Fix bugs 1.0.0 (2011-11-04) ------------------ * Add node attribute: linecolor * Rename diagram attributes: * fontsize -> default_fontsize * default_line_color -> default_linecolor * default_text_color -> default_textcolor * Add docutils extention * Fix bugs 0.9.7 (2011-11-01) ------------------ * Add node attribute: fontsize * Add edge attributes: thick, fontsize * Add group attribute: fontsize * Change color of shadow in PDF mode * Add class feature (experimental) * Add handler-plugin framework (experimental) 0.9.6 (2011-10-22) ------------------ * node.style supports dashed_array format style * Fix bugs 0.9.5 (2011-10-19) ------------------ * Add node attributes: width and height * Fix bugs 0.9.4 (2011-10-07) ------------------ * Fix bugs 0.9.3 (2011-10-06) ------------------ * Replace SVG core by original's (simplesvg.py) * Refactored * Fix bugs 0.9.2 (2011-09-30) ------------------ * Add node attribute: textcolor * Add group attribute: textcolor * Add edge attribute: textcolor * Add diagram attributes: default_text_attribute * Fix beginpoint shape and endpoint shape were reversed * Fix bugs 0.9.1 (2011-09-26) ------------------ * Add diagram attributes: default_node_color, default_group_color and default_line_color * Fix bugs 0.9.0 (2011-09-25) ------------------ * Add icon attribute to node * Make transparency to background of PNG images * Fix bugs 0.8.9 (2011-08-09) ------------------ * Fix bugs 0.8.8 (2011-08-08) ------------------ * Fix bugs 0.8.7 (2011-08-06) ------------------ * Fix bugs 0.8.6 (2011-08-01) ------------------ * Support Pillow as replacement of PIL (experimental) * Fix bugs 0.8.5 (2011-07-31) ------------------ * Allow dot characters in node_id * Fix bugs 0.8.4 (2011-07-05) ------------------ * Fix bugs 0.8.3 (2011-07-03) ------------------ * Support input from stdin * Fix bugs 0.8.2 (2011-06-29) ------------------ * Add node.stacked * Add node shapes: dots, none * Add hiragino-font to font search list * Support background image fetching from web * Add diagram.edge_layout (experimental) * Fix bugs 0.8.1 (2011-05-14) ------------------ * Change license to Apache License 2.0 * Fix bugs 0.8.0 (2011-05-04) ------------------ * Add --separate option and --version option * Fix bugs 0.7.8 (2011-04-19) ------------------ * Update layout engine * Update requirements: PIL >= 1.1.5 * Update parser for tokenize performance * Add --nodoctype option * Fix bugs * Add many testcases 0.7.7 (2011-03-29) ------------------ * Fix bugs 0.7.6 (2011-03-26) ------------------ * Add new layout manager for portrait edges * Fix bugs 0.7.5 (2011-03-20) ------------------ * Support multiple nodes relations (cf. A -> B, C) * Support node group declaration at attribute of nodes * Fix bugs 0.7.4 (2011-03-08) ------------------ * Fix bugs 0.7.3 (2011-03-02) ------------------ * Use UTF-8 characters as Name token (by @swtw7466) * Fix htmlentities included in labels was not escaped on SVG images * Fix bugs 0.7.2 (2011-02-28) ------------------ * Add default_shape attribute to diagram 0.7.1 (2011-02-27) ------------------ * Fix edge has broken with antialias option 0.7.0 (2011-02-25) ------------------ * Support node shape 0.6.7 (2011-02-12) ------------------ * Change noderenderer interface to new style * Render dashed ellipse more clearly (contributed by @cocoatomo) * Support PDF exporting 0.6.6 (2011-01-31) ------------------ * Support diagram.shape_namespace * Add new node shapes; mail, cloud, beginpoint, endpoint, minidiamond, actor * Support plug-in structure to install node shapes * Fix bugs 0.6.5 (2011-01-18) ------------------ * Support node shape (experimental) 0.6.4 (2011-01-17) ------------------ * Fix bugs 0.6.3 (2011-01-15) ------------------ * Fix bugs 0.6.2 (2011-01-08) ------------------ * Fix bugs 0.6.1 (2011-01-07) ------------------ * Implement 'folded' attribute for edge * Refactor layout engine 0.6 (2011-01-02) ------------------ * Support nested groups. 0.5.5 (2010-12-24) ------------------ * Specify direction of edges as syntax (->, --, <-, <->) * Fix bugs. 0.5.4 (2010-12-23) ------------------ * Remove debug codes. 0.5.3 (2010-12-23) ------------------ * Support NodeGroup.label. * Implement --separate option (experimental) * Fix right-up edge overrapped on other nodes. * Support configration file: .blockdiagrc 0.5.2 (2010-11-06) ------------------ * Fix unicode errors for UTF-8'ed SVG exportion. * Refactoring codes for running on GAE. 0.5.1 (2010-10-26) ------------------ * Fix license text on diagparser.py * Update layout engine. 0.5 (2010-10-15) ------------------ * Support background-image of node (SVG) * Support labels for edge. * Fix bugs. 0.4.2 (2010-10-10) ------------------ * Support background-color of node groups. * Draw edge has jumped at edge's cross-points. * Fix bugs. 0.4.1 (2010-10-07) ------------------ * Fix bugs. 0.4 (2010-10-07) ------------------ * Support SVG exporting. * Support dashed edge drawing. * Support background image of nodes (PNG only) 0.3.1 (2010-09-29) ------------------ * Fasten anti-alias process. * Fix text was broken on windows. 0.3 (2010-09-26) ------------------ * Add --antialias option. * Fix bugs. 0.2.2 (2010-09-25) ------------------ * Fix edge bugs. 0.2.1 (2010-09-25) ------------------ * Fix bugs. * Fix package style. 0.2 (2010-09-23) ------------------ * Update layout engine. * Support group { ... } sentence for create Node-Groups. * Support numbered badge on node (cf. A [numbered = 5]) 0.1 (2010-09-20) ----------------- * first release blockdiag-1.5.3/e.diag0000644000076600000240000000007412234636060015315 0ustar tkomiyastaff00000000000000{ plugin autoclass; default_shape = circle; A -> B; } blockdiag-1.5.3/examples/0000755000076600000240000000000012556430037016063 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/examples/blockdiagrc0000644000076600000240000000011011635000760020233 0ustar tkomiyastaff00000000000000[blockdiag] fontpath = /usr/share/fonts/truetype/kochi/kochi-mincho.ttf blockdiag-1.5.3/examples/group.diag0000644000076600000240000000017011635000760020034 0ustar tkomiyastaff00000000000000diagram { group { A -> B -> C -> D; } C -> E; A -> F -> G -> H; group { G; H; } } blockdiag-1.5.3/examples/group.png0000644000076600000240000002364211635000760017725 0ustar tkomiyastaff00000000000000PNG  IHDRD'iIDATxO$I130->x ^7"-+q Z{;3=pi ĭETNUWeEF==#"#)D-'anf&Crc7?0808080808080phfGc7b [XGGw= \,} 00/V@@@ D׳Ǿ8"N4+   ND׳8+  ƹࢰ~c091b_\G7%c>qxO8he8uyAYwvxgMj+xu @<&3p13~e@XWہڃ/k`ަmaMPk֗Y競#jIKkTU4M+T:si4ge4~X5xI!/*vffb 83p=~@1mcFH4F7 >Igfo^ĉ"NDE\Q8;+B{O3ZbeVm$VbB >QrUv\-Ԃ Ebg3p =~e@_fbōĵĵwR6F:oZ/ŕgJS'~I@b?r[1\'*hVYXI\KTVJz-q#VmPB~pu\!?װjg/s-+.grUeb&6$ lV?Y^)ǕEHBOھ9I++?SMDՙzr8w2 ;EwRݍ+7bH o 9Q/?Z*Q悺 ҅G_, 3p?~@w 22nmG[n^߯"G^|?_ƯKQ9Զ#EZ+G{#;$nĢсu>x޷R:Y~j'pP?pmq-"J K^K/ik0>_x_9l56R w~kwovY܈t~Kҥot&A΋KW"puպ{ s̟_y݉Ģ+nzgwI7f3|IT?<ڙ~J0}P?xUWH܈R+mV].y%\̟ t.W4k*(VmN+?HׁbÇ'^TdiP7C[*JDZYz#tE31X=3e 8nēlb֔Ư DD18bčMXmM|1x{U'}<'~i}Џہa~TTUiVKfQҼ-Sy/ĈX`<_t ҺmEbfY-Vo;Ψ}tkκ_El? >9%F  s:zdl?w#`<_x_wa?C3h͓,T$ھҶY#է^~=x-2 NA^HMTTM$FgͺLwN`$RІĨ1i` Mvj@tMƶd^?m*j3n{mShNF^_ Az?Q%ݙ8|`͹to޼чS RcB vb5׶HU׵8ԍ6}~Gsq*HAPMN9ը#9^ٽtIJU̟ }Ltje@tVs!DSmAMLsZ|#bi҇ A0cĞfo:_bp̂q{%[>P?A7>/ѩ:#m{hYADsl9aB&@=s(}8̧[CmרSsVǴƯS GLhM.+]u[Uiu6T$~>oBT?ؙYFS}7c`,zl꺮z`I`'/1렜< %R*Wut:PIrRPAU*ݦv@TLbDjZ=>s>DǯdD]`rdouW(aD~G&v/Nr?hH6MUU;_ݴ`/15¤x0&u1qE6>QExlʳ|Z^1A?g }Lw&Sxv/CJmc Ì;$]}P?Dܿk4C3Kc܏2?:̟>&:~%S HnkdLTyިA,{/R`ci̟>&=~M1HF1bGAٟ^a Džs6/dlL6V~""`v}P?8\h6ȇnOgsO&e3~vS?dٴ^,5k~_}W_/__}_|quu5ϋpiG{c33hom[۞0FĈ-dGJn>p1ִYά~|NfZ~`ju{n)@-(?kQ60dΦ~5COnoocUZݴ7Ƭ@`q b&4O}ᴜS>9s9 'dZqooǟ8ӱr; /p`ñ[pl씨qfcݜwww7v9_݄# :'CGGGGGGGGGGѱI~"/\y}P?8/JpS+#6G^A#'9P՝q'Xt/Hȳ;'̤V>߷4GQA9~>59th8\8 1vv0nc/zԶ5b}JvID,5穟QQ?A*O/9s/g\9N0Aa<_cWmOiNv}!x[x;Y~Z6.:j4M/b~'s)SS\\w;q*.pN;#3{zFGK7Q:Pa9*~xFx[18Nĥv?_w߽~իW?ëW^~ۻ,Ƙ!NթFNwR?C~7c`HīܟA>8m sBQEQeYeUU"⽏1O{l'?S 9_xsZ9tsNԩǩsNS]]Yy'I1I9 Nī^A - -wm/&nLt4#TDjQ3/v~*jb"Qw>"(3P?CL3+eZ}:Hţ9O3|>7"8bX=pd>$̟Ǖg&ǯ$}5^\Hk(BYyUĹridbTj,u37/}Y"{uVoLT>ȿ\~áUin?UEU7B(Pr'ZC3]O׏^f.m9E^Lu<6 Ӈr҇(u]G]xrD1Zyd9_O~Wj:(b sRL\EV&C(`$cqOETͫ+^^|̅E|T5?W=|Kd%~u3֝5}5]&&jNs޹0b)ƨ6ԏx,E)s].bVeQ9ΫKoǛ/(b6-tEQj٤BR1O~}>쳫b1R1̟s}#:q^\!0Z덚z- sK&,*]e.NTT~94>C\UY,jVlʲ>ufwo}֍?ҐזlۋLTM\T_K_!hI DC AqRz",|Qs_\( ^5soLDJBb^]@qi*.?iמ\]]]__c5sZy=EUh!pވ:(dQyb_n]m-XK($MFjK}h=/eE]|9gM}ϻ؆6u?$;TęZ5~^:x31gAVn1/|Y1҅R}>nd+ t_>p@ bX,rb62qWWNXzO׭R\•k\҅[+־emRWܼvUS_x #Sqb .>B8uӍaSqbˏvPy|7}ij δإkL$:*S?C~3!?c~9b60L+ su8WԪuH'6- ڇvѵV{p:0`&n1ASp4mk.+5,Lb%"JZIHl_kݬTGF5=>V| Μ?Mp;Ё>o=Zg*&b׎9㨟iAmHz]Q2̯ϸڅR]!,df3"ޔa>!-.@(E()huӇ1'̟}L~j@-,,j$,ԕ͜uhEŔwP*"jN̩xg^w来˓tR4B?6KDi~s2'~2A>t& _}qkW^kP? m7'҄42#ڹE"I;P2?cWW6q^-+5DgR\kQ-Fgf&$myMYcS~.TM^2mj|^tGG߁|=Uק93~Nd2OjnhJ s W,4,ԕAoh8t-4g>AuwLIPk'z.̟}LtڑUeBUg.hS NuO ՚zIw.OuǿZVCfJOHSj+P9#O4J'z8ԛ>50AZY "*ՉzuABVE3&iV-O?L}hyο~>ЇzWR?c~^l;$N8. A5 ~ޖǴmCJrquӶt0Ms2Iَ^T5:`V"&foW1Ayh~e*WQ?~^9KTtD8۫g]nKXSwb!~0 /7gѬ%&✈m_%]|<^}FbygRb"f{k;s0Mu\6^5I: ja+_Ci@'y~h3b೯Ed'83-۩sn4kJև( y%&:~@lAѦUUM;=hO,uG+ǐ 3!/;Fŕ}x?[\W_}x&Q?YjGjqyKocĉnwC#'0~e)ld?n铊}u0y"rXl-?:il{8>&oS2 Z`̮lN_t883̟ 1y^A~r5fMpAp@?nwS 뱛5z"{&9Bwsm3z}`p_"C@ D@SJ 080q.`! V8m8q^н#8##;\F. >+  ƹ 'zJLp<Ӱ1`Őg 'xX0I^3@LI5?ɘ~@O#c6R T5bx\y`Dñ% f Ţ/Do޼чS qރ.a4XCX0*U]׮Cc r׽!7B)&}8"83.1ȻwD96i Ǜ7oftD%}'@d{6}jvAnnnbZD@ȐG htƋq~*AiD "|ܤ!PeYEQ"@%9x捈f|>u]"a#W1D]0zl6u]usaCn๺jZV ]?vC )zl6UU\|1v _S)n<dp~@&e 1v` s(Lf|>v+(B$a)BD޽{7vC@Urp}}_݊#`Xof&BVQt_Y,BሺI˲m~{V /.XpssZ޿?vCx 7  Z Swwwgv~f9On8`p`p`p`p`p`p`p`p`p`pKM)!Nzs;E|7h- c7՞r;.sn'} DUׯ_z~xׯ߾}{{{{wwWU7ٸ6SHqCa6-K؏!!D9!EQEYeYVU%"=QFzCLNq{)z|وH* EzS |@9mJ2EQ뺮 $= <@n⋢(fbJW^EZ6M *E 퓟r>jXfAp!<`>zND (w= n/`W@u-" )}`tHbX,r؍L@dJUͬ@:nonI> ݃c֑nѽ]71v #}^Dڛ2>h%en0vK _=[\@qD;hA$)}`J(ܥk1s;48AuwLIPk'z  =NYC:&z8M &`CJrquyxƘ@LC:݉!b$<`Jr"p2ݣ@D0I'^ _OvW=p><"|x&I0-nG%y Yb{@}g`p`p`p0}\V@@@b   Nl63 0808080808080808080y IENDB`blockdiag-1.5.3/examples/group.svg0000644000076600000240000001262212362413524017740 0ustar tkomiyastaff00000000000000 blockdiag A B C D F G H E blockdiag-1.5.3/examples/multibyte.diag0000644000076600000240000000033111635000760020715 0ustar tkomiyastaff00000000000000diagram { A [label = "朝食"]; B [label = "昼食"]; C [label = "おやつ"]; D [label = "夕飯"]; E [label = "夜食"]; "起" -> "承" -> "転" -> "結"; A -> B -> D -> E; B -> C -> D; } blockdiag-1.5.3/examples/multibyte.png0000644000076600000240000001354511635000760020610 0ustar tkomiyastaff00000000000000PNG  IHDR],IDATxQnTa;uҤ-BhYX#*FZ&iLO~ T$u۶0 7L&b 1`B 0!L&b 1`B 0!L&b 1`B ڶ:}A?9Cs&b {yߛ~8B?9C L&2{Gqߛ~CC?}}`B 0!pr3)!~ȡr\>W`B 0!~Q}o%C?9LH}#W QεB!~H7V`~Gvdž~ʤr!G~^i8*B!~@\L۶݇8SC?9So۶b͚{}](0OQCC?O'o}Z 9SX[tx (!~ȡr/Pu4֯B!~NK%XW/IARiC~Jr!G~ϟ?_m4MUU٬SC?9r^@ _G$]+yO9CC?O'.jZmUUk)~ȡr!yK;gXi1JUOQCC?O_*@9RM,b1w][[SC?9"Ӊ7/9JXUU4r\nt$E )~ȡriH{]ilv`U^?e9C#J?  h^/VC?9D+CoUJǟYsIj#*P?{^=;D<< %o}w}9oCow[.ŕOtީv{A!Vz+n.y~bqǏ什gS|{3lMl_y4Ml<\?.#f.y<f8ÓpGnz_7;֢s+áqތ?ɅOW/6N%J?B_$xٽzP̜||n!zC[%~.#\?Ąpryka9rqӊI gw^=un9Nk^o7[?ߵ\~j'doM^[:CEkНyd5쏔9XuK#tOY=G^ވXJqꢄ:xzNzBu!% H'GEr׀"0L&b 1`B 0!LӨzM 0C?9{G"e;M?9CTJr{""9C#t?~ݎg6fPm!~$PUU]OO>_|s۶.}s۶nmt])ionn^%gحN?nmtvk_?Uѳi|>bx~~j۵m+v{4i7M0PCC?G?@H+\.fig*t#D~ȡr'4}j~T|>zznxH1P!~WEEz jMWzl6vR)nu~]V(B~r!G~ 4jzzzJ@7:N}U0I?9Cc4tBv*/;zQnZNCo+3,C?9fmۺw[UznJo7:N݃xݓ߿n~ r!G~fªޔ\.Si\:.t"~Co3 C?9FO'ߒ8I ~r!7uݾ|V[ðތgխAݩ^)~ȡrqoH _aW~Jr!G~:! 1VHzy 4!~ȡr/Pٽàr OQCC?O'9.(D>O CC?O}c2?!q`Gk;?B\w~ȎOJ)_;_?+y|%|/Ώ4nSC?9̆rFrUe׶ȡvU/Ͳ5ևxc!~8+`B 0!7r!~ȡ.LHI9OL~ȡrcJ b ) SŮxgC;C"[`BFrB=B!G~}" ߸JZL/h)~ȡrDN.pD(*B!ǹ 'c{/m׵+~JrqLQm!7kr-u]eT?E9CpIkjzsi-p OQC#\?p*!唉&i_F]=SC?DYz}iV/gi'QSC?P>vCo ϟ/ڶmlVm)~ȡrN%P<>>^HHZC?9"'fh{ЭCojZVm5SK?9Bft2dnLS.)~ȡrN%PUilfX,|ڂ⦟r!G~$ K<>>VU4r\.6-IQ'QSC?P -%HZ6vn{VU)L!~8~CzY9!~u. !Til6{O޴7ӡr:$l_[:l!G~ SQZnwt蔿rA| HCC?LVZE3e;M?9bc _4g<I?4ŧe3 !n<\7-CgZg\8I񤿶=G:Suj\@wͱk*~駡"[ZR_|*w?_\~:ӥRu7Ov_!0+wo ӽ{y +n?0E3Co´t'JN?[|"V ;|֢dpx$_>㏷r>f'+uG}׋pϏ?~O[H?7J??P~d4p<<<g y]G%t?ޫ%g dѿ`S˗nlސ; 8O6~/gps#'9dzbƫWQ9z=:y g4O샏 =zF(ׯ_Gcߺg4?`OG:h9)z|nwSQ|#`B 0!L&b 1`B 0!N7CC?LPA[(K7C?d=P\=Co!~f x@uk6fճ%Ц~ȡr?_ӧ˗/_~}~~nvۥonvح[r4Ջep$ gحn|@@wfi|>_,n׶4EMDYF3C?G?pQHN{Z:rl6UU5Ml=tZݞpX=3C?M?/ɴzܤs>?==m[ O\~wwwsssd -d=O C#n?pr<|~}}Znooӕ|^7MZ@+wOTZ ~ju}}b/gX!~8x@rճij5$'].Co3C?M?p!3(z|޻\V777gPWgX!~80@]mֽ}PZwK_:wҝ>\wm'}BjwemO!C#b?pa=!M2It b:NzKߤ!9߷zzBX(IZ= ~r#N%PuIjoKnt䬺TVgo,m O C#n?pB;ZaK-}UK~Jrq9onV5T?9CX#so,9\1 _:SC?D 9 blockdiag 朝食 昼食 おやつ 夕飯 夜食 blockdiag-1.5.3/examples/numbered.diag0000644000076600000240000000016611635000760020506 0ustar tkomiyastaff00000000000000diagram { A [numbered = 1]; B [numbered = 2]; C [numbered = 3]; D [numbered = 4]; A -> B -> C; A -> D; } blockdiag-1.5.3/examples/numbered.png0000644000076600000240000001062711635000760020371 0ustar tkomiyastaff00000000000000PNG  IHDR^IDATx͎ D(dlG:t6}1sYs1ٌ"f+HK4 Yt &UBIT~\.AQ t+CD @ 0(`P@ @ 0(`P@ @ 0(`D(: +a8f@ 5`1u ho 0(`HXB~f 0(`l{v=&7~xϿ :_Xq>Yu6$xfg*m:goӻ/3g ?Ei7koƜ6>֏G~CNJּ %:O<_~{1du. ?h뗠8.FpwTWZMgɏ_h;?seHџ5sF{Q(y|ۑ/l~pz駾ӡ NJj**9nS1?Vy8ȏ.8gwŇgߒO;iOQ债yi)?>(wɏ .yHnB\Yqy7 ? ?hs!j ]U.K0Z~-nxUi n-(_ i}e1(COE͏Fdỳўz?)""T/о1?_β,hnZM% gS$m>G0iPq2I$IǣR^1:<͏pW6hX|av+1ȏp>wNBɏpeu (`H*X,:Ϗtt+O~?gYv8\C~D~P/9˧VJmۢ(<ν[O܏>qA̳6eݻwm^}ǟ:|̏0^XGu~.`u_C8|/`Џd"= AG(`I7i%I2NGь'm͢Ő{)ңhY"K]A~t`ȏ8_}O/۷C|Ϗ}$F]L\> ,L|{0 ߼y?믿˛7o~l6L8?}0 ަ:g?Nu8q|:^ȏ 7?gTş8q$L_8M,X}iիl$x<ȏ,:| X+:Q%I`N?Xwl>wA~PGocxY3'z%&ItZ>GAMAP}Y_?F$=';@̵6K@UvuO'AJ)sSd2ק?-0֓vGCGA=ȏeO~I'@I};~D~PGc eg<ͺ !8kqA'@g]?Dc'M}:͏e"Ƚ,{OPVȏk+?6/ XYJsDxɏS.?rBς/ȏ 7?6}҃߃?_==/lhw.o|>7>qwߧA~pPz  /AsmuA~*f@ 5`]PAa .^x? n|㇏!=\1\)`"t c,lHpó_v}L 0ss|Q~J*:?49n>5` B5_cfpT#3`x4j~wWzѬW1u3,`Y `g5R@E\qu? N95nW'S fR{3`W^4.F~[#? eBAJ!O3`E +Uy^vV1^.Wz/X@3` ٷ*<=EQ P.=y?ukJ\o0|.=ZVc0٥_[~-v Ӿ7 C/;A,:icP(` v-"{;sq|Bbh(` rLtټ{ý<˓!` $ݵEzD@G(` zKAcd2~ID)^Rɻ"^cpׯGQhʧ>bz  1{s!  ]?EGQM ^7x||H3>1,&,ˤ.1i鍖 Av}j>-`:`P@ @ 0(`P@g:a(` }ApD` (`t1P@_Va/O pAӧbq˗b۷fCQy/#xk26xO L(` 8q$L_y8M,t+Vl|}իl$x<̅h !*n($n#l6/. SG/W/ u'I$t:-π  AP ,I깯R3זآo/AyQ"{27N&ݾzS"`n_18v$}ln_@P}-ZYܬ2mU?ꥃc0ZToǂ0A18]Yv/SK h! XO(7.Ջ!1\壼Y!FggŠP}Il| pg 6>8J!%hז80(`P` }ú//0@ Xf@ 5`u_^c 0(0@ 0(`P@ @ 8'-IENDB`blockdiag-1.5.3/examples/numbered.svg0000644000076600000240000000703312362413524020405 0ustar tkomiyastaff00000000000000 blockdiag A 1 B 2 D 4 C 3 blockdiag-1.5.3/examples/screen.diag0000644000076600000240000000244112556277367020210 0ustar tkomiyastaff00000000000000diagram admin { top_page [label = "Top page", color = "pink"]; group foo { foo_index [label = "List of FOOs"]; foo_detail [label = "Detail FOO", style = "dashed"]; foo_add [label = "Add FOO"]; foo_add_confirm [label = "Add FOO (confirm)"]; foo_edit [label = "Edit FOO"]; foo_edit_confirm [label = "Edit FOO (confirm)"]; foo_delete_confirm [label = "Delete FOO (confirm)"]; } group bar { bar_detail [label = "Detail of BAR", style = "dotted"]; bar_edit [label = "Edit BAR"]; bar_edit_confirm [label = "Edit BAR (confirm)"]; } logout; top_page -> foo_index [color = "red", dir = none, label = "button"]; top_page -> bar_detail [style = dashed, label = "link"]; foo_index -> foo_detail; foo_detail -> foo_edit; foo_detail -> foo_delete_confirm; foo_index -> foo_add -> foo_add_confirm; foo_add_confirm -> foo_index [label = "added"]; foo_index -> foo_edit -> foo_edit_confirm; foo_edit_confirm -> foo_index [label = "changed"]; foo_index -> foo_delete_confirm; foo_delete_confirm -> foo_index [label = "deleted"]; bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail [dir = both, style = dotted]; foo_index -> foo_edit [style = dotted]; } blockdiag-1.5.3/examples/screen.png0000644000076600000240000005436111635000760020052 0ustar tkomiyastaff00000000000000PNG  IHDR_XIDATxM$[vs~~ hXd 6l)hc AQ$a"CedZ^$ e0h^_Uxq#"?+2+F7="?**T97f&΃{pF(3B Pg8#`wX|7g+$Rl! Py}uҕڟ4<\-u]q8#00lՕKkT6=-o}[<4 [ kWi.@8ms6 Nǖ|gn+sx!tcMWv)!3g{ c޿}wm X]>l?:v0O `mUpJa;?K0\$'vP #~(0H٬M> :o`v0t7 Nn%%84&듍tz+QX蚥4QY-l!}t~]OjR)91k2XX{#Σ[ڬo 1Py4%!YoVJ9p򍈶Z?NPfAB%̪N x8*қWǏ?D?"'* Y)6PUuPW2?٫7N U.\&"Re^$tjX0~J&VYKIYu-a&LlnaR+Ů>{p\fN7 p-bDm]Hv 0wR^Zy)ՕT3 sdӃV[*ћ~ԉfrfSyT;%@8I&f*Rw2mW2{m[)HN:<7N Uܢ9mD +TJEV]KyiWvB_Hu%\i; aUܢnw^|.~,#)""她L]&ŎFN ۤӬ]k~aW^Yy)Рɏ7WO}sz!v {}H=?`+9kzf ɦZ<0V^j602WQ'fCIHvUwFZIu%[r\_JFk s`C+6yw]ꮇǭN-v{) !~ݽXJ-\~$# 3цYu3PsJ@d+T3)^Kb Zsyڮczán' )t vpJ /.² 8*]l0!'RS(طĂܪUWR^JodZwf球xWۨu.w7x2KUx?6pJ|TKB >ɒ8!;fGM|ǭ 4wbp-ՕU\)o~ƪw}m̱œ;nkXJ*+ED\յU3"R0 b&I![tN qa2 J00oUÍ!@ YYO[b[޴-ʚu/5XRQU b"Kby 6_]јj]3E7.ՔA$4*ʬ7C|]>RmًX3 v wˍs ~McV?HEDUTԜYPI77,`RofBub&wDM_B@.6t|kBzn4kϵ0[Iך,쪻m5r.F@gc}ž"~IKA\29˗(e@Lng?_>}o~yWUtn)9.ݒm6xx'6Rس֟R6ŏ6?5Qgw&nvP-Yt89_#?<\G H"Y[[U?a6~{wi9L?]/"'?8MoJ`3UWDS [oIq7 v!SpuuY2@쿻ڮAeK(uUWj^;q"N$hjkBHzp!~S#~p{q޹,SmTWH? xM0,D94 Nk e Yf9WJ*7L\`#'6~$?۹Z3N&ֵm!~@`kG!~|HQrY8ߥ feY]/^L&dRUt t`i~uZ;=*g$B."k&lL73얂p[{!~W8dBr.8H1mc(I)'O?X^@;$V!ݿ]1˪Pq*ޙwM퀛 '?g?͋w|2-|^ilXDo`Pŋ1ϲ((<!$8jb&Ϯ28ro4[_ B$aA򜬍fD?xq]eyW PȺR>HD/_~۷޽UUN35oк ,3.Ug+.YED>5[?[?:~IvG @7)M&l6k vuo9w`bD@γ̅q}4ֽ;)so6~nγ|.1~U+Gz-T,>%ue7MT)mҏ3? n X-7iR@`kGץ+7\U"&Huڽ',u `\Yi4%WDG6~G$ n.EۆA읡Q)9JED+ hZMfMI7݂?}A G4çk sBXMa6 G~xidž`3]s ,%[(,=sIo lvϾ,"kwާ -Q!Ê".n-Ar #`_{%"K _>~V>7?'0|x}& rP]l_/A~}_E)ODH|g__u?iFCz&()^VHX]MkXnCK\6EC7=sҀqvC~ADy7VcQRQ%ݏ92؀uww&lO[a'v{lqݯZ4RM5 }1x ݣ֑i[}7~8?=ǎ[vX }¦V,%~N};}x)er4R٠{&~ޯᵍ۟|[V5?>gϞ}裏>{b<yӎU;O꽯JV,Ԯ> o>o 8vu_L|eyG;M]\?\?Ӽ9ɝ8'**`/ʫoެNwZ~3kw_.w=b r@9ďts?%/~}MǛeˎSfU}k?G-o?ǞNF=}\<}h(:xv m"f ܤ|+rrfvu{ &.@OpzP)v% ԝgڮ >4h v..?Qt>l҉E<;xhvڦ/5.^ܲru;>Р>_;!ksQt4?G[,uZ?Z[{Drm0CO;}hv3aAp;v @7o$nyo}w{|_Oem3恢K{jv ?w=߭p[m_yúk$gD 8> o ${&N>S}o '8#c 7Wj?c vHpF(8 $sch X#xɗy?R@0F.H:SK;b- RF`bw6vt_;ޢHR7ig e;O[Rs1O ;v61m @_hprsι?2@o4M=yNM\s&n)\ G돳^۟____^^|O?O|'~W.//˲O^}[C{~eh:>yӧϞ=裏={{o:F<ϻ`ݢL,X(%̬+~+8[ջׯ^W2{~9u̮+W "f*\58&i6'❍|dQ'ǣ4 #/_Yu7owwZ6_MI互ҽ_+_>}rzD>.d2gYlg Əe2o"~'}OnG2g I̫81՛j M&<|."YeIppKd2{,H?)yYa8ʠ"S37-(!aUh^iR9 LP0Ͼ!m?ď$ F^s3TTM4~r$ 9e=ۺ:?lVUu/ďѣGtK O=཯0[m;qƾe}t6 擃Һ;]gE^NjU'`sqb,h[e Y5ͪ2B.4בq)DC*?yFdrqq{y~uu5c ?ʷ~?~|qq1Lb;5OZ8sӼ,̅qJ+Sh|5ͪGE9nbWI/*bM䈈o>?S?AB8ɼ3i.&hx@Z?˲d2b6SDwG}o:**bNūF^&YU"YYpe`$N0KBn^rU#AS0ɪ׎D`Ǐ?Xэy+sEeEiK$K<0XL&dӽ.QVFYU{3PO[|2/Wc $y5UvNi TJl䫐yE3e ӏď,ZgIe&NjSM4 N Vv϶Kzlo)ӎuVgLUūe>A).c#\֌jTwmL |M?9 BUi [G֋{9',s< 7]}o`VS,6BH;)x<l:<6ab fCTĚS1'xS Y 2LJ; xZ's\oQB.@M9*5~;_Mw..,˼GduUdM@eKڶjZۧ?rӂTDM81,7,|mOJqMU|Uw;jh3ωt' ~>vu8u;Bw5%9 t4mBhc%ZJSzx` yҎh㊓Nޖ '"*ND8o9d1O ngIA?jΉͽ3 n,d[=}?og{Cuqg8]k{tW. XP;w=p)荭Yl-}7a(FǷc ] 8v2lw NPwu^8aplV&/lG񴉾5p/mX0,mû.iH˽n D"Vg&w7#)@>[h7:ĩ8Z28auSpUWj^ū8'(eNGaMIHY;˝e2X(Nv@b BWyȇF>]p&(x@|mͤ @>Y5ɪ& f^  nHËw4/BEĩxg5Qp<&D*SfgWExgknm R(v @*^r\ea2P}gP ip;$<\~V2he5C$8=Ӏו7 ;'j~̓++-VA_p&(4}ߛv2jf~1Qp<鈭KCGbN;=/8,\Q>|7# iixeou.wU-guW.U;-/K(pf(8NO˧w]}-lZjtQpT? "r˛[5( b>ݮЎvMnwe_Ws 潧' Y:6]m>oyV?wSʾeKwJ@%00N{ p*Y坖̚w__^7~}-?w.Mtߟ\8hzɨs@|+rr~W—un}$c$;YzvyGW覑[ b&w~vrңfj/myyz؂Ic03uK= g>aӬAY} w{"){N pF(3B P i(3B $qQ3$8K,\Qpld)մ8Y9Q%SS5 $8)mOŻw/G/Gwev]*h1S~7TԟF>Lһҫ柪Zl4 JU4&jY,󖻐PZR5ZLB/N-sy˜y #ǙxNCūdr FgUTD2u`jFH**Nk}d8F*\]Ȝy i@Ҽ0t1|lwj^-sV0ΪiV<*+4T;]gE^NjU95if"VgS?/D$sab@ p |XzyjUrM |v^ GEW롨UUʠ4\3 pu('Y5!w^78 .^%w6UE4YU&bP3ɪi^|;Wb7-Q@7#G>SɽP?8Xċq ȇF0=p(4F81EOk\eZ<${*^-s!wX8W?8m6NwB0 1hGęX]Sx\3 hDE1h;8uLI &&f"RsK28M]~&p(4?b&jDD]L۴|ύp(8M M͓Ug{?d8yS{g0+C7% 9`'`Kѽ/h] N s}o }@-QpxTžɣ pFN?}{82 z}(;⟷C $  08#|c`q7 p8U]j_]ʵO+jhڧp-}hʥsXI:Z-=ۗdpIc0`fv}i 8qwV/ ܺ2_4`X(`/f GK\Q itORpd*7Z@R(`/\qP}XNgy ~Zp7fnӹEm&~|Ə+څ,m!~ :Ͼ@*bu2@,ŜIʦT8 $H-TUUTSQ]HMވDlY۵ 8-+,RռWqNdqOjO#vfMI KY;˝e2qbepObƏ,I>HPB{; :/|0Subw"~R6~ 5_[3hMgW8&YrԜ5sOƏHZ< . /Ӽ ygD-ыORƏ DHel(,{M!~ұ6~s@ip",̅QY(,G={}ԬPB< $Hup41@ nγ̅qg+V*ޓ7'MkGҋRL )8ו71` vtI;~;k~̓++-VA[O"p(4}ߛ,j~nY_ @dx A:bmhgwPwVƏp(>HRP'mC/C6D,5[|%K}o6HG xI>Hdz/|}oGCUm|7kZ]05>]~toۜUO{|Vޡ_xhL>~â;ω/KܽMڕnCݽ'=~0١>P/HϗbMֶ޵̥!'=׮i%6oW?u= E(4=?Rzeҫ!Pۿ&&=ѫ bB\XM&+e$?p^·lNOf͛//|M}ֶ}|rճ맓'rW#'DUTEE߾1i_ȥqޡp?gwGwuy@_ kN;_dގ}~<]?> 󯋙$լy~ [9>[?0=P iF g _;g;>tVtYvYMmY 07" P i7Pg8#}8#@ҸhA#~pf($oD,r7ҕi0$RM;v>oi8C$?c%{p$|[[^}HD,MVZjmҸoٍ`%?8%l$!HaPݛ5C?F//W׳_W 1!)!k>2U1qjȅI^y7μ?n]>箞dr߮E$-)ؐW!6 lgMWP5ZZnl;TթN3k-VIe`fg` 9L-~ڹk$s:uQś⬵TöI9QWvUĉ8$q*=ݜli͆ b"ATUs9|L\G+-MD%sR&!毲v"^%2d("'%&ω:ٸnP ijqu8/.qh6ioUEU7eY^eE+? '奛L.@|n*Nū΍2{Tȍ _Y,J3ՅY:o1;vOpMRL+@(pnRUgH*1 smp[:uy2dcɧZϲ*8}:~yu4GNG2k1rYQr> w jqi43)+J@3NdPt&r/ď%JH򙈈%* p6^ca78Ubjޏ2-|MJ7\i.\<(8^2 ,LȧE9EQyY3KUoeCJ&~0xڽ0d$/b IC4_H6O$DD|!յ\,p|lJZݡm˫.>CE͂ʏ gs/e̓%AIXÙW]q^MrWy2_9gΙՃw9V%~ii5?쑌ޓ$/DsUf#}o8gxx__KvqB 3 X` YWvXǥbn NJtEƕ+-,Hi!Sqb1\(|ea 2gĺ±᤼\M T"Rw3+$JDFKD  JhP $#cn2""B+ s HSp=?>;S1S5ՠrypJcn$òSor[BCi'u n}@ S*/Hx#)})K6W˴һ9NՏ,U&Y6T3 D1ؑ3Aś7INx(*"jN̩xg^wZMI[_'b'$lj &]HX'MՏD8%hSQ$jB`"^HGR$?P;,x1SolSkzn%y?&MkVfn"~x yms~lŹFkdD\.i݌ V[[蹁%>bfLٙ)(I8U_DNmk=c ӑSn_NX3^;Rr Ćs7@ri_ٺ A Hgcgt?INsjI)8 n6[ϭK=֢ ےvӡ*]\1|8Uݩ~y?m!:.|4c+cq(pr}kq,%bmf0QDgMoAI@ykM-.oONOXfeYwǏ?߾M߻|UDT00nG7 lL,XPYӭW?/&Vt֤NՏx楨U;zNү8Mqp&7$f\B)Ve@] @S^ 0DMZ.\&"2/D~qz, ffV]KI5[Xмj6;u^4_+ԏ u8/T]#qvho߂RL++IyiTWR$ʺ_:ٷ{+`X:c|ՉfrfwWͣZ;fLP$IJ 3+_쵕o|H"Ah}o MݘĹ?hvac3Hqj(p?ܪk)/mʮ_ U"mG7 ;KH'bHyi.Sd{)|pb1b`$bڮ>r]=2{eT^@+xc`C:o)y3ƽ>psMx"uC?dSu#qg f<cppտ3ׂX%L+)]rR7R]KZ˿[}ožv=m}^wwnSO!tN]Տ${da&"?T3ɪb"͘17NN5sUbsfRri (Ūۏ]=?`So)ݮKy~ۓ:b+,ĬAܪUWR^JodZwf}G@E1.w7٘% *<;Gyq/JD$%]5RCcȚ@Xx*R]YNʕ`8>kz]\L؉{[X@TT%xq˵j&1 q`4QtY A+%-ۯb5pOϿClgȕ*[jm˛v[Yfz@TTUfjX$1TQ`w.M#[3֭6 " PUfXU s w.}^H ]#;]_5wmA>[9nn#&O F>NzDknL5y:(F!NW]3Iܦs%i%jٽA+нNԍ58&*jzvy858:V7۳i؛İ?o>:oS]ێj ۫_Fqh6Ͼ|O#~_UQ'fLge0IZjY ^^c,xYDf8iLT\۠{mi9a1~p|JTu^uN5hlW_z/ND$7n?.lz@sF**^ͫx'NĉnyDVG`p`^w. *AT=uHc5c33{;.tJT΍,m2 ?۹]W .kƕ'iiHU]i~=)c[@̞,gWWZ)[-p:h{}2uf]γ*W5_^Z({@bc>WLϮWٛWʕA۩LnC|k&AUx3uUA?~Ankqib ҵ6˿߳~g U޿i^VSμ3omĴWHR@`/S˴ya/0\^DM4TW-gWEW8LCMۙH %>^\]]g ]V~FU_͖K n"U;?;D_uif<,};2 ̕..[w3id?yv9'6?vTQ wZ{먵խwUςzgTM1c ץWn^. ED>;0'+pξ<2hl{d%;!~2yкtm;8y8S&*蛪6n?-VܛnO ,^8Te_T8 LvLVp%yg>Ab xp}Z큳{_U-pZܜCF`qϛnc R {}op tBbCy=[*0}AiqmݒwXz-|X&?^ݳ/|;1r:IpxPqU7""&PP[?ȿS=No[Wtk k~o|r9德u:Ij0yG/o{088SϽ^0 Wqyu͎WWnϠ|9y~ADyyV~w 5qӈU -L~L,X(%̬+~?_t|m/'>>mW$w✨H]$NNmCB֗HOq3ڗw)[6{MT[{釮xĹB\DUEۘX*W +8G7>GKgljo>ݒ#$yuy0R [?{z=B`c VϯoyZ:n9[>v~h\[ٳ'r$rf"  /<‰jt ~]Mvo.KO[vCҁ bWFOF_3I0Y 0mhRI'<'=zX贏?[P,g]x[NKg-ZmSYeÎ&I6~=nI63R[pǗJyV_<3 " g3G8> 66>8#Yz8> 5pF(p/>g>Z;3%yasBWt{P1. N NR꟩ #Z.*7)̏-j^j!>r?|<[k\k?~yu4GNG2k1rYQr> w jqiS..ew[LD:sBS!~}tGZmI!:dQ^yyu^]h\&LN/4s5ZdUFM}VeY>&U4b TTĩ8LC8 Ӽ(iQyQT.fo|ե[Cvِ}XXݝmjޏ2-G`qRx2I6GI^}>rY\&E]#%N NR݃SՋ &=bb0?˲*e+\m"IPVyh?m E"Fɣ|^6~Ņfcuys1AL?2qfSK(ED\GR^IKj;4nTiG8wg.+4k6qDB5SBq3F3ωYՙ4_ jQ< KMYz17oCj͔9> eeҹ!@;ߍ'G;W'"jeFUjZ03Lmr3N9O\;4Zi~?} ~poͬ@uNSD3L\o~~ Q/HfR "&fM^,m<ڣC妗6+ws/ooDkQqNꤡ9+9b!WqҫOŁ pJeumNWS꾷kӉ$wzn m #'Iu %NNjˍv~ ~ž)9J;V$p.,-T| Nq{ZW Ζ{pFRxȁ` g8#Ie @ +pFRxDp8#I Ў):$nHY>u->R0`nI#Rv hsTijYfIAA\29˗(eAj Cd M?Sl:e;@jWt;7,e4. WH?@޼y7,2q1 A,(RQ# feY]/^L&dRUt}1-bƉ~}x  ),D /^eEQEy! ^D/_Heh<㪪bq C*~e Y&l6UUUUހ>ڔ )C-wFb?˲\`P 3U }.2BXM;ipDޣ-F.5S_5  c{`0(-? YvlzZ۸~ >7djSvy˂lIof3{ 1k'pQ۝ZpX%o7\4 XLg!uL;]^}7!|TQg8#pF(3B Pg8#8 U{pa$)Pե~Hְ ` 6$s9S4@DTw~w>O>;'|駟z,K3 !'Y[;tmN,Ft:2j&9}eyy^EQeY>`f~YgYFd9x<sɲ,K [*&Icw?@TĜ2fkt@P1o?Xw=~b2FX M+ fYM&l  3~v\\\ blockdiag Top page List of FOOs Detail FOO Add FOO Edit FOO Delete FOO (confirm) Add FOO (confirm) Edit FOO (confirm) Detail of BAR Edit BAR Edit BAR (confirm) logout link button added changed deleted blockdiag-1.5.3/examples/simple.diag0000644000076600000240000000011011635000760020163 0ustar tkomiyastaff00000000000000diagram { A -> B -> C -> D; C -> E; A -> F -> G -> H; } blockdiag-1.5.3/examples/simple.png0000644000076600000240000001452511635000760020062 0ustar tkomiyastaff00000000000000PNG  IHDR@(9 IDATx]nFPR~((E{ߥuYS74 iX?.&Ndlq^+(1pp8*☍=Ls_u?2u]wr9:+p(p(p\zܐ|B~!?x}oCVQQsܐTJ~#?̾!+p(p(pn~96d    O0I7 ?$?rm @0 @0?B?Y.D9kD~ !?rOpa~mOC(yVQ)OS!#\~b>sJ9qS-p'ﺮ0氮U{Ӊ(29 YYov\K韧ˠk@~"?rPW=ݲk]]׏ePlC!G`Ǯ-S!#b~z p PY.jZt$EOiCX99(Azmnw8ɪ xG~$?rDϳfc 1#n+ C~4?I/o=nOڋr9"%!Ċ~%?er9 ]i;/?+!?rOQ;*9:Su(%8M~!?䈛*z>}أEC!Gy72Zfڶ]cucZ9&Kcz)pOc6d&S}iL~/1LO~~@`3vLA=?@#ן9TC~Fg%b, 8O ǻw~__~yݏ?lV|>f%K_<͎M>/߿ez|1p70v=1ۀX&.lb1Epdܑ\O>n7@^ b&afb&_#RFp2]Z>vn{࢘R~Ҝ#?4T_P(p#8 }ybyb?Nϔ~|@0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 c[9A=Sɉr^sC8J^=""r9B{߯?бflvOJNC~4?IWUU]}ӧO?]/w]7h34r\7O/%ϸN~mtOz64M3bX,u迢7Mx~4Q.C~1LZVni~xu'@۶鰧 闟Q9OcluJ|>||<.W7qLv^(z9qH.79mfVvvxU)O?lڶ].(v!Z~%?rDωx.9Ri1]kUԃ7{/Q9O/d;9UUJRm x;O{oNm؃}K~!?䈞0ctGޥ}q~mñH q,?r9"%a ܉adZۗn"5쑾H~F$?rL ?nx/i?{xEÛ}L_PC~1 +pu]obWOߪNx }ySJN_5 ?%r9D' e=ѿ`*;S!#n~z! \}!T5A˼4C!G ,pӑ= *H_x(C!G(O; YD~J ?rO)~&p'#G*p_9û#/?+OH+w"?rg66 @0?BL!C~xSVQQ)q}r9䇋LP "ļg9*@0 @0+p%Homp89YU\Wvn_joSrqӱ8aObUD x~8@^-@Y 駟+olJEoc8x^C#cn <+'~bq)mSr;@MKxvc)Pmmۉi8xC?Xř؏FFFFFFFFFFFW]c{(ppiAKu}r"Fc4cK_b5f줽E9) \Z]}ӧO?]/w]7h;YkM,z}ƕOl6if>bX,}UU777:iœt W8TOnjZv ܫ;)pmۦÞj\:Q[[K gzNm>?>> >a]_p%9.|>_.mn6t>on \e۫JU?7ͦmr:\ \yKimStW1Ɛf=/pc)pp!+pá^djo66ڶmv^=ᶪT / d~M{u~r :|گۍ=g(pp9E"WUjoi ,-G8aܒڛN-ޢP)ppYj5xfM_Iu+)pp9}H]-u?c_ \ z۸M1?(pp9_e8 ([-Q[BC(ٍwxzÔ> }E>]h 1{|+pDe?_WX| >-7*@0 @023Qe<*O1=pL(t}'3ICr) @0 @0'(7B9GZ†WsI~%?R:2g$?rˏ\LufT~J ?䈛J:̘]ֵJe?iuNDӨE~.?=3+۶{8Ri-p9gHuO*wq60F=C)p\ Sg ͓i4Z=͞OisBqP}q뺦ifnC9gH@|HzQC~1?=0|۫=㢺o۶mPUpJiT~%?(p\~MW,㺻l6YچNS!G )p\#h94bX,x<[~!?䈘u>0ᡪijZV!MIQ7S!GPj~6 ֶn;dU&?erDϳfc1Dvn/͞ 1k ?Dc*Mnߟ<{h/*C~1?=ÓXSg%?erOq]v<ϧN#CI8`Lw=""rΏuND9R;~/*|Ҕ9 K8H۶zQKc*L5?cZL&?'(p\۟~iQ6@~o><~_C!%o6,z~ E%P(偗 LC 0i EAyXzyqKݻ__޽{?n6j5gn/o,?=8(?27ktn߱BHSOWꞪ))p\O>n7@i2:[h%P |)'y:=QL)?B*|ybS~9&`b|@0 @0 @0 @0 @0 @0 @0 @0 @0 @0 @0 0Vo%?ONDe&?䈛Jƕz`#t~/XWflv2{R uA(p\믻?߿çO>ﻮ;_nFωilf\'hgF7T שPgM4|>/b﫪9]י:_Q؛Y{ϐq9gH*uup[C-޷5Dh9^>B~1?/QFÛVU=o'Ĵp{/9&^՗["5">8Ig;gDC gH㺤gu2>o&Nz'Sgis@~7?'8Q2]æ ;FN)#n~z W>Plp&SgsF~+?C ר~Vk:S~"?:J*\,D7)#n~*LOGk?B\ǵʅ: }=GW||["?lm8` blockdiag A B F C D E G H blockdiag-1.5.3/LICENSE0000644000076600000240000002613611635000760015253 0ustar tkomiyastaff00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. blockdiag-1.5.3/MANIFEST.in0000644000076600000240000000051512431560416016001 0ustar tkomiyastaff00000000000000include buildout.cfg include bootstrap.py include CHANGES.rst include MANIFEST.in include LICENSE include blockdiag.1 include tox.ini include src/blockdiag/tests/VLGothic/* recursive-include examples blockdiagrc *.diag *.png *.svg recursive-include src README *.py *.diag *.gif *.png exclude .drone.io.sh exclude examples/update.sh blockdiag-1.5.3/PKG-INFO0000644000076600000240000001132612556430040015337 0ustar tkomiyastaff00000000000000Metadata-Version: 1.1 Name: blockdiag Version: 1.5.3 Summary: blockdiag generates block-diagram image from text Home-page: http://blockdiag.com/ Author: Takeshi Komiya Author-email: i.tkomiya at gmail.com License: Apache License 2.0 Download-URL: http://pypi.python.org/pypi/blockdiag Description: `blockdiag` generate block-diagram image file from spec-text file. .. image:: https://drone.io/bitbucket.org/blockdiag/blockdiag/status.png :target: https://drone.io/bitbucket.org/blockdiag/blockdiag :alt: drone.io CI build status .. image:: https://pypip.in/v/blockdiag/badge.png :target: https://pypi.python.org/pypi/blockdiag/ :alt: Latest PyPI version .. image:: https://pypip.in/d/blockdiag/badge.png :target: https://pypi.python.org/pypi/blockdiag/ :alt: Number of PyPI downloads Features ======== * Generate block-diagram from dot like text (basic feature). * Multilingualization for node-label (utf-8 only). You can get some examples and generated images on `blockdiag.com `_ . Setup ===== Use easy_install or pip:: $ sudo easy_install blockdiag Or $ sudo pip install blockdiag If you want to export as PDF format, give pdf arguments:: $ sudo easy_install "blockdiag[pdf]" Copy and modify ini file. example:: $ cp /blockdiag/examples/simple.diag . $ vi simple.diag Please refer to `spec-text setting sample`_ section for the format of the `simpla.diag` configuration file. spec-text setting sample ======================== Few examples are available. You can get more examples at `blockdiag.com`_ . simple.diag ------------ simple.diag is simply define nodes and transitions by dot-like text format:: diagram admin { top_page -> config -> config_edit -> config_confirm -> top_page; } screen.diag ------------ screen.diag is more complexly sample. diaglam nodes have a alternative label and some transitions:: diagram admin { top_page [label = "Top page"]; foo_index [label = "List of FOOs"]; foo_detail [label = "Detail FOO"]; foo_add [label = "Add FOO"]; foo_add_confirm [label = "Add FOO (confirm)"]; foo_edit [label = "Edit FOO"]; foo_edit_confirm [label = "Edit FOO (confirm)"]; foo_delete_confirm [label = "Delete FOO (confirm)"]; bar_detail [label = "Detail of BAR"]; bar_edit [label = "Edit BAR"]; bar_edit_confirm [label = "Edit BAR (confirm)"]; logout; top_page -> foo_index; top_page -> bar_detail; foo_index -> foo_detail; foo_detail -> foo_edit; foo_detail -> foo_delete_confirm; foo_index -> foo_add -> foo_add_confirm -> foo_index; foo_index -> foo_edit -> foo_edit_confirm -> foo_index; foo_index -> foo_delete_confirm -> foo_index; bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail; } Usage ===== Execute blockdiag command:: $ blockdiag simple.diag $ ls simple.png simple.png Requirements ============ * Python 2.6, 2.7, 3.2, 3.3, 3.4 * Pillow 2.2.1 or later * funcparserlib 0.3.6 or later * reportlab (optional) * wand and imagemagick (optional) * setuptools License ======= Apache License 2.0 Keywords: diagram,generator Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Documentation Classifier: Topic :: Text Processing :: Markup blockdiag-1.5.3/README.rst0000644000076600000240000000562012407311355015733 0ustar tkomiyastaff00000000000000`blockdiag` generate block-diagram image file from spec-text file. .. image:: https://drone.io/bitbucket.org/blockdiag/blockdiag/status.png :target: https://drone.io/bitbucket.org/blockdiag/blockdiag :alt: drone.io CI build status .. image:: https://pypip.in/v/blockdiag/badge.png :target: https://pypi.python.org/pypi/blockdiag/ :alt: Latest PyPI version .. image:: https://pypip.in/d/blockdiag/badge.png :target: https://pypi.python.org/pypi/blockdiag/ :alt: Number of PyPI downloads Features ======== * Generate block-diagram from dot like text (basic feature). * Multilingualization for node-label (utf-8 only). You can get some examples and generated images on `blockdiag.com `_ . Setup ===== Use easy_install or pip:: $ sudo easy_install blockdiag Or $ sudo pip install blockdiag If you want to export as PDF format, give pdf arguments:: $ sudo easy_install "blockdiag[pdf]" Copy and modify ini file. example:: $ cp /blockdiag/examples/simple.diag . $ vi simple.diag Please refer to `spec-text setting sample`_ section for the format of the `simpla.diag` configuration file. spec-text setting sample ======================== Few examples are available. You can get more examples at `blockdiag.com`_ . simple.diag ------------ simple.diag is simply define nodes and transitions by dot-like text format:: diagram admin { top_page -> config -> config_edit -> config_confirm -> top_page; } screen.diag ------------ screen.diag is more complexly sample. diaglam nodes have a alternative label and some transitions:: diagram admin { top_page [label = "Top page"]; foo_index [label = "List of FOOs"]; foo_detail [label = "Detail FOO"]; foo_add [label = "Add FOO"]; foo_add_confirm [label = "Add FOO (confirm)"]; foo_edit [label = "Edit FOO"]; foo_edit_confirm [label = "Edit FOO (confirm)"]; foo_delete_confirm [label = "Delete FOO (confirm)"]; bar_detail [label = "Detail of BAR"]; bar_edit [label = "Edit BAR"]; bar_edit_confirm [label = "Edit BAR (confirm)"]; logout; top_page -> foo_index; top_page -> bar_detail; foo_index -> foo_detail; foo_detail -> foo_edit; foo_detail -> foo_delete_confirm; foo_index -> foo_add -> foo_add_confirm -> foo_index; foo_index -> foo_edit -> foo_edit_confirm -> foo_index; foo_index -> foo_delete_confirm -> foo_index; bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail; } Usage ===== Execute blockdiag command:: $ blockdiag simple.diag $ ls simple.png simple.png Requirements ============ * Python 2.6, 2.7, 3.2, 3.3, 3.4 * Pillow 2.2.1 or later * funcparserlib 0.3.6 or later * reportlab (optional) * wand and imagemagick (optional) * setuptools License ======= Apache License 2.0 blockdiag-1.5.3/setup.cfg0000644000076600000240000000041112556430040016054 0ustar tkomiyastaff00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [build] build-base = _build [sdist] formats = gztar [wheel] universal = 1 [aliases] release = check -r -s register sdist bdist_wheel upload [check] strict = 1 restructuredtext = 1 [flake8] ignore = _ blockdiag-1.5.3/setup.py0000644000076600000240000000704612524564653015775 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- import sys from setuptools import setup, find_packages sys.path.insert(0, 'src') import blockdiag classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: System Administrators", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "Topic :: Software Development", "Topic :: Software Development :: Documentation", "Topic :: Text Processing :: Markup", ] requires = ['setuptools', 'funcparserlib', 'Pillow'] pdf_requires = ['reportlab'] test_requires = ['nose', 'mock', 'pep8>=1.3', 'reportlab', 'docutils'] # only for Python2.6 if (2, 6) < sys.version_info < (2, 7): requires.append('OrderedDict') pdf_requires[0] = 'reportlab < 3.0' test_requires.append('unittest2') # webcolors if (3, 2) < sys.version_info < (3, 3): requires.append('webcolors < 1.5') # webcolors-1.5 does not support py32 else: requires.append('webcolors') setup( name='blockdiag', version=blockdiag.__version__, description='blockdiag generates block-diagram image from text', long_description=open("README.rst").read(), classifiers=classifiers, keywords=['diagram', 'generator'], author='Takeshi Komiya', author_email='i.tkomiya at gmail.com', url='http://blockdiag.com/', download_url='http://pypi.python.org/pypi/blockdiag', license='Apache License 2.0', py_modules=['blockdiag_sphinxhelper'], packages=find_packages('src'), package_dir={'': 'src'}, package_data={'': ['buildout.cfg']}, include_package_data=True, install_requires=requires, extras_require=dict( testing=test_requires, pdf=pdf_requires, rst=[ 'docutils', ], ), test_suite='nose.collector', tests_require=test_requires, entry_points=""" [console_scripts] blockdiag = blockdiag.command:main [blockdiag_noderenderer] box = blockdiag.noderenderer.box square = blockdiag.noderenderer.square roundedbox = blockdiag.noderenderer.roundedbox diamond = blockdiag.noderenderer.diamond minidiamond = blockdiag.noderenderer.minidiamond mail = blockdiag.noderenderer.mail note = blockdiag.noderenderer.note cloud = blockdiag.noderenderer.cloud circle = blockdiag.noderenderer.circle ellipse = blockdiag.noderenderer.ellipse beginpoint = blockdiag.noderenderer.beginpoint endpoint = blockdiag.noderenderer.endpoint actor = blockdiag.noderenderer.actor flowchart.database = blockdiag.noderenderer.flowchart.database flowchart.input = blockdiag.noderenderer.flowchart.input flowchart.loopin = blockdiag.noderenderer.flowchart.loopin flowchart.loopout = blockdiag.noderenderer.flowchart.loopout flowchart.terminator = blockdiag.noderenderer.flowchart.terminator textbox = blockdiag.noderenderer.textbox dots = blockdiag.noderenderer.dots none = blockdiag.noderenderer.none [blockdiag_plugins] attributes = blockdiag.plugins.attributes autoclass = blockdiag.plugins.autoclass [blockdiag_imagedrawers] imagedraw_png = blockdiag.imagedraw.png imagedraw_svg = blockdiag.imagedraw.svg imagedraw_pdf = blockdiag.imagedraw.pdf """, ) blockdiag-1.5.3/src/0000755000076600000240000000000012556430037015034 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/0000755000076600000240000000000012556430037016753 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/__init__.py0000644000076600000240000000117312556430015021062 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. __version__ = '1.5.3' blockdiag-1.5.3/src/blockdiag/builder.py0000644000076600000240000006334012425172474020764 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag import parser from blockdiag.elements import Diagram, DiagramNode, NodeGroup, DiagramEdge from blockdiag.plugins import fire_node_event from blockdiag.utils import unquote, XY from blockdiag.utils.compat import cmp_to_key class DiagramTreeBuilder: def build(self, tree, config): self.config = config self.diagram = Diagram() self.instantiate(self.diagram, tree) for subgroup in self.diagram.traverse_groups(): if len(subgroup.nodes) == 0: subgroup.group.nodes.remove(subgroup) self.bind_edges(self.diagram) self.fire_node_event('build_finished') return self.diagram def fire_node_event(self, event_type): for node in self.diagram.nodes: if node.drawable: fire_node_event(node, event_type) def is_related_group(self, group1, group2): if group1.is_parent(group2) or group2.is_parent(group1): return True else: return False def belong_to(self, node, group): if node.group and node.group.level > group.level: override = False else: override = True if node.group and node.group != group and override: if not self.is_related_group(node.group, group): msg = "could not belong to two groups: %s" % node.id raise RuntimeError(msg) old_group = node.group parent = group.parent(old_group.level + 1) if parent: if parent in old_group.nodes: old_group.nodes.remove(parent) index = old_group.nodes.index(node) old_group.nodes.insert(index + 1, parent) old_group.nodes.remove(node) node.group = None if node.group is None: node.group = group if node not in group.nodes: group.nodes.append(node) def instantiate(self, group, tree): for stmt in tree.stmts: # Translate Node having group attribute to Group if isinstance(stmt, parser.Node): group_attr = [a for a in stmt.attrs if a.name == 'group'] if group_attr: group_id = group_attr[-1] stmt.attrs.remove(group_id) if group_id.value != group.id: stmt = parser.Group(group_id.value, [stmt]) # Instantiate statements if isinstance(stmt, parser.Node): node = DiagramNode.get(stmt.id) node.set_attributes(stmt.attrs) self.belong_to(node, group) elif isinstance(stmt, parser.Edge): from_nodes = [DiagramNode.get(n) for n in stmt.from_nodes] to_nodes = [DiagramNode.get(n) for n in stmt.to_nodes] for node in from_nodes + to_nodes: self.belong_to(node, group) for node1 in from_nodes: for node2 in to_nodes: edge = DiagramEdge.get(node1, node2) edge.set_dir(stmt.edge_type) edge.set_attributes(stmt.attrs) elif isinstance(stmt, parser.Group): subgroup = NodeGroup.get(stmt.id) subgroup.level = group.level + 1 self.belong_to(subgroup, group) self.instantiate(subgroup, stmt) elif isinstance(stmt, parser.Attr): group.set_attribute(stmt) elif isinstance(stmt, parser.Extension): if stmt.type == 'class': name = unquote(stmt.name) Diagram.classes[name] = stmt elif stmt.type == 'plugin': self.diagram.set_plugin(stmt.name, stmt.attrs, config=self.config) elif isinstance(stmt, parser.Statements): self.instantiate(group, stmt) group.update_order() return group def bind_edges(self, group): for node in group.nodes: if isinstance(node, DiagramNode): group.edges += DiagramEdge.find(node) else: self.bind_edges(node) class DiagramLayoutManager: def __init__(self, diagram): self.diagram = diagram self.circulars = [] self.heightRefs = [] self.coordinates = [] def run(self): if isinstance(self.diagram, Diagram): for group in self.diagram.traverse_groups(): self.__class__(group).run() self.edges = DiagramEdge.find_by_level(self.diagram.level) self.do_layout() self.diagram.fixiate() if self.diagram.orientation == 'portrait': self.rotate_diagram() def rotate_diagram(self): for node in self.diagram.traverse_nodes(): node.xy = XY(node.xy.y, node.xy.x) node.colwidth, node.colheight = (node.colheight, node.colwidth) if isinstance(node, NodeGroup): if node.orientation == 'portrait': node.orientation = 'landscape' else: node.orientation = 'portrait' xy = (self.diagram.colheight, self.diagram.colwidth) self.diagram.colwidth, self.diagram.colheight = xy def do_layout(self): self.detect_circulars() self.set_node_xpos() self.adjust_node_order() height = 0 for node in self.diagram.nodes: if node.xy.x == 0: self.set_node_ypos(node, height) height = max(xy.y for xy in self.coordinates) + 1 def get_related_nodes(self, node, parent=False, child=False): uniq = {} for edge in self.edges: if edge.folded: continue if parent and edge.node2 == node: uniq[edge.node1] = 1 elif child and edge.node1 == node: uniq[edge.node2] = 1 related = [] for uniq_node in uniq.keys(): if uniq_node == node: pass elif uniq_node.group != node.group: pass else: related.append(uniq_node) related.sort(key=lambda x: x.order) return related def get_parent_nodes(self, node): return self.get_related_nodes(node, parent=True) def get_child_nodes(self, node): return self.get_related_nodes(node, child=True) def detect_circulars(self): for node in self.diagram.nodes: if not [x for x in self.circulars if node in x]: self.detect_circulars_sub(node, [node]) # remove part of other circular for c1 in self.circulars[:]: for c2 in self.circulars: intersect = set(c1) & set(c2) if c1 != c2 and set(c1) == intersect: if c1 in self.circulars: self.circulars.remove(c1) break if c1 != c2 and intersect: if c1 in self.circulars: self.circulars.remove(c1) self.circulars.remove(c2) self.circulars.append(c1 + c2) break def detect_circulars_sub(self, node, parents): for child in self.get_child_nodes(node): if child in parents: i = parents.index(child) if parents[i:] not in self.circulars: self.circulars.append(parents[i:]) else: self.detect_circulars_sub(child, parents + [child]) def is_circular_ref(self, node1, node2): for circular in self.circulars: if node1 in circular and node2 in circular: parents = [] for node in circular: for parent in self.get_parent_nodes(node): if parent not in circular: parents.append(parent) for parent in sorted(parents, key=lambda x: x.order): children = self.get_child_nodes(parent) if node1 in children and node2 in children: if circular.index(node1) > circular.index(node2): return True elif node2 in children: return True elif node1 in children: return False else: if circular.index(node1) > circular.index(node2): return True return False def set_node_xpos(self, depth=0): for node in self.diagram.nodes: if node.xy.x != depth: continue for child in self.get_child_nodes(node): if self.is_circular_ref(node, child): pass elif node == child: pass elif child.xy.x > node.xy.x + node.colwidth: pass else: child.xy = XY(node.xy.x + node.colwidth, 0) depther_node = [x for x in self.diagram.nodes if x.xy.x > depth] if len(depther_node) > 0: self.set_node_xpos(depth + 1) def adjust_node_order(self): for node in list(self.diagram.nodes): parents = self.get_parent_nodes(node) if len(set(parents)) > 1: for i in range(1, len(parents)): node1 = parents[i - 1] node2 = parents[i] if node1.xy.x == node2.xy.x: idx1 = self.diagram.nodes.index(node1) idx2 = self.diagram.nodes.index(node2) if idx1 < idx2: self.diagram.nodes.remove(node2) self.diagram.nodes.insert(idx1 + 1, node2) else: self.diagram.nodes.remove(node1) self.diagram.nodes.insert(idx2 + 1, node1) children = self.get_child_nodes(node) if len(set(children)) > 1: for i in range(1, len(children)): node1 = children[i - 1] node2 = children[i] idx1 = self.diagram.nodes.index(node1) idx2 = self.diagram.nodes.index(node2) if node1.xy.x == node2.xy.x: if idx1 < idx2: self.diagram.nodes.remove(node2) self.diagram.nodes.insert(idx1 + 1, node2) else: self.diagram.nodes.remove(node1) self.diagram.nodes.insert(idx2 + 1, node1) elif self.is_circular_ref(node1, node2): pass else: if node1.xy.x < node2.xy.x: self.diagram.nodes.remove(node2) self.diagram.nodes.insert(idx1 + 1, node2) else: self.diagram.nodes.remove(node1) self.diagram.nodes.insert(idx2 + 1, node1) if isinstance(node, NodeGroup): children = self.get_child_nodes(node) if len(set(children)) > 1: while True: exchange = 0 for i in range(1, len(children)): node1 = children[i - 1] node2 = children[i] idx1 = self.diagram.nodes.index(node1) idx2 = self.diagram.nodes.index(node2) ret = self.compare_child_node_order(node, node1, node2) if ret > 0 and idx1 < idx2: self.diagram.nodes.remove(node1) self.diagram.nodes.insert(idx2 + 1, node1) exchange += 1 if exchange == 0: break self.diagram.update_order() def compare_child_node_order(self, parent, node1, node2): def compare(x, y): x = x.duplicate() y = y.duplicate() while x.node1 == y.node1 and x.node1.group is not None: x.node1 = x.node1.group y.node1 = y.node1.group # cmp x.node1.order and y.node1.order if x.node1.order < y.node1.order: return -1 elif x.node1.order == y.node1.order: return 0 else: return 1 edges = (DiagramEdge.find(parent, node1) + DiagramEdge.find(parent, node2)) edges.sort(key=cmp_to_key(compare)) if len(edges) == 0: return 0 elif edges[0].node2 == node2: return 1 else: return -1 def mark_xy(self, xy, width, height): for w in range(width): for h in range(height): self.coordinates.append(XY(xy.x + w, xy.y + h)) def set_node_ypos(self, node, height=0): for x in range(node.colwidth): for y in range(node.colheight): xy = XY(node.xy.x + x, height + y) if xy in self.coordinates: return False node.xy = XY(node.xy.x, height) self.mark_xy(node.xy, node.colwidth, node.colheight) def cmp(x, y): if x.xy.x < y.xy.y: return -1 elif x.xy.x == y.xy.y: return 0 else: return 1 count = 0 children = self.get_child_nodes(node) children.sort(key=cmp_to_key(cmp)) grandchild = 0 for child in children: if self.get_child_nodes(child): grandchild += 1 prev_child = None for child in children: if child.id in self.heightRefs: pass elif node.xy.x >= child.xy.x: pass else: if isinstance(node, NodeGroup): parent_height = self.get_parent_node_ypos(node, child) if parent_height and parent_height > height: height = parent_height if (prev_child and grandchild > 1 and (not self.is_rhombus(prev_child, child))): coord = [p.y for p in self.coordinates if p.x > child.xy.x] if coord and max(coord) >= node.xy.y: height = max(coord) + 1 while True: if self.set_node_ypos(child, height): child.xy = XY(child.xy.x, height) self.mark_xy(child.xy, child.colwidth, child.colheight) self.heightRefs.append(child.id) count += 1 break else: if count == 0: return False height += 1 height += 1 prev_child = child return True def is_rhombus(self, node1, node2): ret = False while True: if node1 == node2: ret = True break child1 = self.get_child_nodes(node1) child2 = self.get_child_nodes(node2) if len(child1) != 1 or len(child2) != 1: break elif node1.xy.x > child1[0].xy.x or node2.xy.x > child2[0].xy.x: break else: node1 = child1[0] node2 = child2[0] return ret def get_parent_node_ypos(self, parent, child): heights = [] for e in DiagramEdge.find(parent, child): y = parent.xy.y node = e.node1 while node != parent: y += node.xy.y node = node.group heights.append(y) if heights: return min(heights) else: return None class EdgeLayoutManager(object): def __init__(self, diagram): self.diagram = diagram @property def groups(self): if self.diagram.separated: seq = self.diagram.nodes else: seq = self.diagram.traverse_groups(preorder=True) for group in seq: if not group.drawable: yield group @property def nodes(self): if self.diagram.separated: seq = self.diagram.nodes else: seq = self.diagram.traverse_nodes() for node in seq: if node.drawable: yield node @property def edges(self): for edge in (e for e in self.diagram.edges if e.style != 'none'): yield edge for group in self.groups: for edge in (e for e in group.edges if e.style != 'none'): yield edge def run(self): for edge in self.edges: _dir = edge.direction if edge.node1.group.orientation == 'landscape': if _dir == 'right': r = range(edge.node1.xy.x + 1, edge.node2.xy.x) for x in r: xy = (x, edge.node1.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir == 'right-up': r = range(edge.node1.xy.x + 1, edge.node2.xy.x) for x in r: xy = (x, edge.node1.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir == 'right-down': if self.diagram.edge_layout == 'flowchart': r = range(edge.node1.xy.y, edge.node2.xy.y) for y in r: xy = (edge.node1.xy.x, y + 1) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 r = range(edge.node1.xy.x + 1, edge.node2.xy.x) for x in r: xy = (x, edge.node2.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir in ('left-down', 'down'): r = range(edge.node1.xy.y + 1, edge.node2.xy.y) for y in r: xy = (edge.node1.xy.x, y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir == 'up': r = range(edge.node2.xy.y + 1, edge.node1.xy.y) for y in r: xy = (edge.node1.xy.x, y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 else: if _dir == 'right': r = range(edge.node1.xy.x + 1, edge.node2.xy.x) for x in r: xy = (x, edge.node1.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir in ('left-down', 'down'): r = range(edge.node1.xy.y + 1, edge.node2.xy.y) for y in r: xy = (edge.node1.xy.x, y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 elif _dir == 'right-down': if self.diagram.edge_layout == 'flowchart': r = range(edge.node1.xy.x, edge.node2.xy.x) for x in r: xy = (x + 1, edge.node1.xy.y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 r = range(edge.node1.xy.y + 1, edge.node2.xy.y) for y in r: xy = (edge.node2.xy.x, y) nodes = [x for x in self.nodes if x.xy == xy] if len(nodes) > 0: edge.skipped = 1 class ScreenNodeBuilder: @classmethod def build(cls, tree, config=None, layout=True): DiagramNode.clear() DiagramEdge.clear() NodeGroup.clear() Diagram.clear() return cls(tree, config, layout).run() def __init__(self, tree, config, layout): self.diagram = DiagramTreeBuilder().build(tree, config) self.config = config self.layout = layout def run(self): if self.layout: DiagramLayoutManager(self.diagram).run() self.diagram.fixiate(True) EdgeLayoutManager(self.diagram).run() return self.diagram class SeparateDiagramBuilder(ScreenNodeBuilder): @property def _groups(self): # Store nodes and edges of subgroups nodes = {self.diagram: self.diagram.nodes} edges = {self.diagram: self.diagram.edges} levels = {self.diagram: self.diagram.level} for group in self.diagram.traverse_groups(): nodes[group] = group.nodes edges[group] = group.edges levels[group] = group.level groups = {} orders = {} for node in self.diagram.traverse_nodes(): groups[node] = node.group orders[node] = node.order for group in self.diagram.traverse_groups(): yield group # Restore nodes, groups and edges for g in nodes: g.nodes = nodes[g] g.edges = edges[g] g.level = levels[g] for n in groups: n.group = groups[n] n.order = orders[n] n.xy = XY(0, 0) n.colwidth = 1 n.colheight = 1 n.separated = False for edge in DiagramEdge.find_all(): edge.skipped = False edge.crosspoints = [] yield self.diagram def _filter_edges(self, edges, parent, level): filtered = {} for e in edges: if e.node1.group.is_parent(parent): if e.node1.group.level > level: e = e.duplicate() if isinstance(e.node1, NodeGroup): e.node1 = e.node1.parent(level + 1) else: e.node1 = e.node1.group.parent(level + 1) else: continue if e.node2.group.is_parent(parent): if e.node2.group.level > level: e = e.duplicate() if isinstance(e.node2, NodeGroup): e.node2 = e.node2.parent(level + 1) else: e.node2 = e.node2.group.parent(level + 1) else: continue filtered[(e.node1, e.node2)] = e return filtered.values() def run(self): for i, group in enumerate(self._groups): base = self.diagram.duplicate() base.level = group.level - 1 # bind edges on base diagram (outer the group) edges = (DiagramEdge.find(None, group) + DiagramEdge.find(group, None)) base.edges = self._filter_edges(edges, self.diagram, group.level) # bind edges on target group (inner the group) subgroups = group.traverse_groups() edges = sum([g.edges for g in subgroups], group.edges) group.edges = [] for e in self._filter_edges(edges, group, group.level): if isinstance(e.node1, NodeGroup) and e.node1 == e.node2: pass else: group.edges.append(e) # clear subgroups in the group for g in group.nodes: if isinstance(g, NodeGroup): g.nodes = [] g.edges = [] g.separated = True # pick up nodes to base diagram nodes1 = [e.node1 for e in DiagramEdge.find(None, group)] nodes1.sort(key=lambda x: x.order) nodes2 = [e.node2 for e in DiagramEdge.find(group, None)] nodes2.sort(key=lambda x: x.order) nodes = nodes1 + [group] + nodes2 for i, n in enumerate(nodes): n.order = i if n not in base.nodes: base.nodes.append(n) n.group = base if isinstance(group, Diagram): base = group DiagramLayoutManager(base).run() base.fixiate(True) EdgeLayoutManager(base).run() yield base blockdiag-1.5.3/src/blockdiag/command.py0000644000076600000240000000420312520570634020741 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import sys import blockdiag import blockdiag.builder import blockdiag.drawer import blockdiag.parser from blockdiag.utils.bootstrap import Application, Options class BlockdiagOptions(Options): def build_parser(self): super(BlockdiagOptions, self).build_parser() self.parser.add_option( '-s', '--separate', action='store_true', help='Separate diagram images for each group (SVG only)' ) class BlockdiagApp(Application): module = blockdiag def parse_options(self, args): self.options = BlockdiagOptions(self.module).parse(args) def build_diagram(self, tree): if not self.options.separate: return super(BlockdiagApp, self).build_diagram(tree) else: DiagramBuilder = self.module.builder.SeparateDiagramBuilder DiagramDraw = self.module.drawer.DiagramDraw basename = re.sub('.svg$', '', self.options.output) for i, group in enumerate(DiagramBuilder.build(tree)): outfile = '%s_%d.svg' % (basename, i + 1) draw = DiagramDraw(self.options.type, group, outfile, fontmap=self.fontmap, antialias=self.options.antialias, nodoctype=self.options.nodoctype, transparency=self.options.transparency) draw.draw() draw.save() return 0 def main(args=sys.argv[1:]): return BlockdiagApp().run(args) blockdiag-1.5.3/src/blockdiag/drawer.py0000644000076600000240000001513512555656373020632 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from collections import defaultdict from blockdiag import imagedraw, noderenderer from blockdiag.metrics import AutoScaler, DiagramMetrics class DiagramDraw(object): shadow_colors = defaultdict(lambda: (0, 0, 0)) shadow_colors['PNG'] = (64, 64, 64) shadow_colors['PDF'] = (144, 144, 144) def __init__(self, _format, diagram, filename=None, **kwargs): self.format = _format.upper() self.diagram = diagram self.fill = kwargs.get('fill', (0, 0, 0)) self.badgeFill = kwargs.get('badgeFill', 'pink') self.filename = filename self.shadow = self.shadow_colors[self.format.upper()] if self.format == 'PNG' and kwargs.get('antialias'): self.scale_ratio = 2 else: self.scale_ratio = 1 self.drawer = imagedraw.create(self.format, self.filename, filters=['linejump'], scale_ratio=self.scale_ratio, **kwargs) self.metrics = self.create_metrics(kwargs.get('basediagram', diagram), drawer=self.drawer, fontmap=kwargs.get('fontmap')) if self.scale_ratio == 2: self.metrics = AutoScaler(self.metrics, scale_ratio=self.scale_ratio) self.drawer.set_canvas_size(self.pagesize()) self.drawer.set_options(jump_radius=self.metrics.cellsize / 2) def create_metrics(self, *args, **kwargs): return DiagramMetrics(*args, **kwargs) @property def nodes(self): for node in self.diagram.traverse_nodes(): if node.drawable: yield node @property def groups(self): for group in self.diagram.traverse_groups(preorder=True): if not group.drawable: yield group @property def edges(self): edges = self.diagram.traverse_edges(preorder=True) for edge in (e for e in edges if e.style != 'none'): yield edge def pagesize(self, scaled=False): if scaled: metrics = self.metrics else: metrics = self.metrics.original_metrics width = self.diagram.colwidth height = self.diagram.colheight return metrics.pagesize(width, height) def draw(self, **kwargs): # switch metrics object during draw backgrounds temp, self.metrics = self.metrics, self.metrics.original_metrics self._draw_background() self.metrics = temp if self.scale_ratio > 1: pagesize = self.pagesize(scaled=True) self.drawer.resizeCanvas(pagesize) self._draw_elements(**kwargs) def _draw_background(self): # Draw node groups. for group in self.groups: if group.shape == 'box': box = self.metrics.group(group).marginbox if group.href and self.format == 'SVG': drawer = self.drawer.anchor(group.href) else: drawer = self.drawer drawer.rectangle(box, fill=group.color, filter='blur') # Drop node shadows. for node in self.nodes: if node.color != 'none' and self.diagram.shadow_style != 'none': r = noderenderer.get(node.shape) shape = r(node, self.metrics) if node.href and self.format == 'SVG': drawer = self.drawer.anchor(node.href) else: drawer = self.drawer shape.render(drawer, self.format, fill=self.shadow, shadow=True, style=self.diagram.shadow_style) def _draw_elements(self, **kwargs): for node in self.nodes: self.node(node, **kwargs) for edge in self.edges: self.edge(edge) for edge in self.edges: self.edge_label(edge) for group in self.groups: if group.shape == 'line': box = self.metrics.group(group).marginbox self.drawer.rectangle(box, fill='none', outline=group.color, style=group.style, thick=group.thick) for node in self.groups: self.group_label(node, **kwargs) def node(self, node, **kwargs): r = noderenderer.get(node.shape) shape = r(node, self.metrics) if node.href and self.format == 'SVG': drawer = self.drawer.anchor(node.href) else: drawer = self.drawer shape.render(drawer, self.format, fill=self.fill, badgeFill=self.badgeFill) def group_label(self, group): m = self.metrics.group(group) font = self.metrics.font_for(group) if group.label and not group.separated: self.drawer.textarea(m.grouplabelbox, group.label, font=font, fill=group.textcolor) elif group.label: self.drawer.textarea(m.corebox, group.label, font=font, fill=group.textcolor) def edge(self, edge): metrics = self.metrics.edge(edge) for line in metrics.shaft.polylines: self.drawer.line(line, fill=edge.color, thick=edge.thick, style=edge.style, jump=True) for head in metrics.heads: if edge.hstyle in ('generalization', 'aggregation'): self.drawer.polygon(head, outline=edge.color, fill='white') else: self.drawer.polygon(head, outline=edge.color, fill=edge.color) def edge_label(self, edge): if edge.label: metrics = self.metrics.edge(edge) font = self.metrics.font_for(edge) self.drawer.textarea(metrics.labelbox, edge.label, font=font, fill=edge.textcolor, outline=self.fill) def save(self, size=None): return self.drawer.save(self.filename, size, self.format) blockdiag-1.5.3/src/blockdiag/elements.py0000644000076600000240000004663612413552576021165 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import re import copy from blockdiag.utils import images, unquote, urlutil, uuid, XY from blockdiag.utils.compat import u from blockdiag.utils.logging import warning from blockdiag import noderenderer, plugins class Base(object): basecolor = (255, 255, 255) textcolor = (0, 0, 0) fontfamily = None fontsize = None style = None int_attrs = ['colwidth', 'colheight', 'fontsize'] @classmethod def set_default_color(cls, color): cls.basecolor = images.color_to_rgb(color) @classmethod def set_default_style(cls, style): cls.style = style @classmethod def set_default_text_color(cls, color): cls.textcolor = images.color_to_rgb(color) @classmethod def set_default_fontfamily(cls, fontfamily): cls.fontfamily = fontfamily @classmethod def set_default_fontsize(cls, fontsize): cls.fontsize = int(fontsize) @classmethod def clear(cls): cls.basecolor = (255, 255, 255) cls.textcolor = (0, 0, 0) cls.fontfamily = None cls.fontsize = None cls.style = None def duplicate(self): return copy.copy(self) def set_attribute(self, attr): name = attr.name value = unquote(attr.value) if name == 'class': if value in Diagram.classes: klass = Diagram.classes[value] self.set_attributes(klass.attrs) else: msg = "Unknown class: %s" % value raise AttributeError(msg) elif hasattr(self, "set_%s" % name): getattr(self, "set_%s" % name)(value) elif name in self.int_attrs: setattr(self, name, int(value)) elif hasattr(self, name) and not callable(getattr(self, name)): setattr(self, name, value) else: class_name = self.__class__.__name__ msg = "Unknown attribute: %s.%s" % (class_name, attr.name) raise AttributeError(msg) def set_attributes(self, attrs): for attr in attrs: self.set_attribute(attr) def set_style(self, value): if re.search('^(?:none|solid|dotted|dashed|\d+(,\d+)*)$', value, re.I): self.style = value.lower() else: class_name = self.__class__.__name__ msg = "unknown %s style: %s" % (class_name, value) raise AttributeError(msg) class Element(Base): namespace = {} int_attrs = Base.int_attrs + ['width', 'height'] @classmethod def get(cls, elemid): if not elemid: elemid = uuid.generate() unquote_id = unquote(elemid) if unquote_id not in cls.namespace: obj = cls(elemid) cls.namespace[unquote_id] = obj return cls.namespace[unquote_id] @classmethod def clear(cls): super(Element, cls).clear() cls.namespace = {} cls.basecolor = (255, 255, 255) cls.textcolor = (0, 0, 0) def __init__(self, elemid): self.id = unquote(elemid) self.label = '' self.xy = XY(0, 0) self.group = None self.drawable = False self.order = 0 self.color = self.basecolor self.width = None self.height = None self.colwidth = 1 self.colheight = 1 self.stacked = False def __repr__(self): _format = "<%s '%s' %s %dx%d at 0x%08x>" params = (self.__class__.__name__, self.id, str(self.xy), self.colwidth, self.colheight, id(self)) return _format % params def set_color(self, color): self.color = images.color_to_rgb(color) def set_textcolor(self, color): self.textcolor = images.color_to_rgb(color) class DiagramNode(Element): shape = 'box' int_attrs = Element.int_attrs + ['rotate'] linecolor = (0, 0, 0) label_orientation = 'horizontal' desctable = [] attrname = {} @classmethod def set_default_shape(cls, shape): cls.shape = shape @classmethod def set_default_linecolor(cls, color): cls.linecolor = images.color_to_rgb(color) @classmethod def clear(cls): super(DiagramNode, cls).clear() cls.shape = 'box' cls.linecolor = (0, 0, 0) cls.label_orientation = 'horizontal' cls.desctable = ['numbered', 'label', 'description'] cls.attrname = dict(numbered='No', label='Name', description='Description') def __init__(self, elemid): super(DiagramNode, self).__init__(elemid) self.label = unquote(elemid) or '' self.numbered = None self.icon = None self.background = None self.description = None self.rotate = 0 self.drawable = True self.href = None plugins.fire_node_event(self, 'created') def set_attribute(self, attr): if plugins.fire_node_event(self, 'attr_changing', attr): super(DiagramNode, self).set_attribute(attr) plugins.fire_node_event(self, 'attr_changed', attr) def set_linecolor(self, color): self.linecolor = images.color_to_rgb(color) def set_shape(self, value): if noderenderer.get(value): self.shape = value else: msg = "unknown node shape: %s" % value raise AttributeError(msg) def set_icon(self, value): if urlutil.isurl(value) or os.path.isfile(value): self.icon = value else: warning("icon image not found: %s", value) def set_background(self, value): if urlutil.isurl(value) or os.path.isfile(value): self.background = value else: warning("background image not found: %s", value) def set_stacked(self, _): self.stacked = True def set_label_orientation(self, value): value = value.lower() if value in ('horizontal', 'vertical'): self.label_orientation = value else: msg = "unknown label orientation: %s" % value raise AttributeError(msg) def to_desctable(self): attrs = [] for name in self.desctable: value = getattr(self, name) if value is None: attrs.append(u("")) else: attrs.append(value) return attrs class NodeGroup(Element): basecolor = (243, 152, 0) @classmethod def clear(cls): super(NodeGroup, cls).clear() cls.basecolor = (243, 152, 0) def __init__(self, elemid): super(NodeGroup, self).__init__(elemid) self.level = 0 self.separated = False self.shape = 'box' self.thick = 3 self.nodes = [] self.edges = [] self.icon = None self.orientation = 'landscape' self.href = None def duplicate(self): copied = super(NodeGroup, self).duplicate() copied.nodes = [] copied.edges = [] return copied def parent(self, level): if self.level < level: return None group = self while group.level != level: group = group.group return group def is_parent(self, other): parent = self.parent(other.level) return parent == other def traverse_nodes(self, preorder=False): for node in self.nodes: if isinstance(node, NodeGroup): if preorder: yield node for subnode in node.traverse_nodes(preorder=preorder): yield subnode if not preorder: yield node else: yield node def traverse_edges(self, preorder=False): if preorder: for edge in self.edges: yield edge for group in self.traverse_groups(preorder): for edge in group.traverse_edges(preorder): yield edge if not preorder: for edge in self.edges: yield edge def traverse_groups(self, preorder=False): for node in self.traverse_nodes(preorder=preorder): if isinstance(node, NodeGroup): yield node def fixiate(self, fixiate_nodes=False): if self.separated: self.colwidth = 1 self.colheight = 1 return elif len(self.nodes) > 0: self.colwidth = max(x.xy.x + x.colwidth for x in self.nodes) self.colheight = max(x.xy.y + x.colheight for x in self.nodes) for node in self.nodes: if fixiate_nodes: node.xy = XY(self.xy.x + node.xy.x, self.xy.y + node.xy.y) if isinstance(node, NodeGroup): node.fixiate(fixiate_nodes) def update_order(self): for i, node in enumerate(self.nodes): node.order = i def set_orientation(self, value): value = value.lower() if value in ('landscape', 'portrait'): self.orientation = value else: msg = "unknown diagram orientation: %s" % value raise AttributeError(msg) def set_shape(self, value): value = value.lower() if value in ('box', 'line'): self.shape = value else: msg = "unknown group shape: %s" % value raise AttributeError(msg) class DiagramEdge(Base): basecolor = (0, 0, 0) namespace = {} @classmethod def get(cls, node1, node2): if node1 not in cls.namespace: cls.namespace[node1] = {} if node2 not in cls.namespace[node1]: obj = cls(node1, node2) cls.namespace[node1][node2] = obj return cls.namespace[node1][node2] @classmethod def find(cls, node1, node2=None): if node1 is None and node2 is None: return cls.find_all() elif isinstance(node1, NodeGroup): edges = cls.find(None, node2) edges = (e for e in edges if e.node1.group.is_parent(node1)) return [e for e in edges if not e.node2.group.is_parent(node1)] elif isinstance(node2, NodeGroup): edges = cls.find(node1, None) edges = (e for e in edges if e.node2.group.is_parent(node2)) return [e for e in edges if not e.node1.group.is_parent(node2)] elif node1 is None: return [e for e in cls.find_all() if e.node2 == node2] else: if node1 not in cls.namespace: return [] if node2 is None: return cls.namespace[node1].values() if node2 not in cls.namespace[node1]: return [] return cls.namespace[node1][node2] @classmethod def find_all(cls): for v1 in cls.namespace.values(): for v2 in v1.values(): yield v2 @classmethod def find_by_level(cls, level): edges = [] for e in cls.find_all(): edge = e.duplicate() skips = 0 if edge.node1.group.level < level: skips += 1 else: while edge.node1.group.level != level: edge.node1 = edge.node1.group if edge.node2.group.level < level: skips += 1 else: while edge.node2.group.level != level: edge.node2 = edge.node2.group if skips == 2: continue edges.append(edge) return edges @classmethod def clear(cls): super(DiagramEdge, cls).clear() cls.namespace = {} cls.basecolor = (0, 0, 0) def __init__(self, node1, node2): self.node1 = node1 self.node2 = node2 self.crosspoints = [] self.skipped = 0 self.label = None self.description = None self.dir = 'forward' self.color = self.basecolor self.hstyle = None self.folded = None self.thick = None def __repr__(self): _format = "<%s '%s' %s - '%s' %s at 0x%08x>" params = (self.__class__.__name__, self.node1.id, self.node1.xy, self.node2.id, self.node2.xy, id(self)) return _format % params def set_dir(self, value): value = value.lower() if value in ('back', 'both', 'none', 'forward'): self.dir = value elif value == '-<': self.dir = 'forward' self.hstyle = 'onemany' elif value == '>-': self.dir = 'back' self.hstyle = 'manyone' elif value == '>-<': self.dir = 'both' self.hstyle = 'manymany' elif value == '->': self.dir = 'forward' elif value == '<-': self.dir = 'back' elif value == '<->': self.dir = 'both' elif value == '--': self.dir = 'none' else: msg = "unknown edge dir: %s" % value raise AttributeError(msg) def set_color(self, color): self.color = images.color_to_rgb(color) def set_hstyle(self, value): value = value.lower() if value in ('generalization', 'composition', 'aggregation'): self.hstyle = value elif value == 'oneone': self.dir = 'none' self.hstyle = value elif value == 'onemany': self.dir = 'forward' self.hstyle = value elif value == 'manyone': self.dir = 'back' self.hstyle = value elif value == 'manymany': self.dir = 'both' self.hstyle = value else: msg = "unknown edge hstyle: %s" % value raise AttributeError(msg) def set_folded(self, _): self.folded = True def set_nofolded(self, _): self.folded = False def set_thick(self, _): self.thick = 3 @property def direction(self): node1 = self.node1.xy node2 = self.node2.xy if node1.x > node2.x: if node1.y > node2.y: _dir = 'left-up' elif node1.y == node2.y: _dir = 'left' else: _dir = 'left-down' elif node1.x == node2.x: if node1.y > node2.y: _dir = 'up' elif node1.y == node2.y: _dir = 'same' else: _dir = 'down' else: if node1.y > node2.y: _dir = 'right-up' elif node1.y == node2.y: _dir = 'right' else: _dir = 'right-down' return _dir def to_desctable(self): label = "%s -> %s" % (self.node1.label, self.node2.label) return [label, self.description] class Diagram(NodeGroup): _DiagramNode = DiagramNode _DiagramEdge = DiagramEdge _NodeGroup = NodeGroup classes = {} shadow_style = 'blur' linecolor = (0, 0, 0) int_attrs = (NodeGroup.int_attrs + ['node_width', 'node_height', 'span_width', 'span_height']) @classmethod def clear(cls): super(Diagram, cls).clear() cls.shadow_style = 'blur' cls.linecolor = (0, 0, 0) cls.classes = {} def __init__(self): super(Diagram, self).__init__(None) self.config = None self.node_width = None self.node_height = None self.span_width = None self.span_height = None self.page_padding = None self.edge_layout = None def set_plugin(self, name, attrs, **kwargs): kwargs.update(dict([str(unquote(attr.name)), unquote(attr.value)] for attr in attrs)) plugins.load([name], diagram=self, **kwargs) def set_plugins(self, value, **kwargs): modules = [name.strip() for name in value.split(',')] plugins.load(modules, diagram=self, **kwargs) def set_default_shape(self, value): if noderenderer.get(value): DiagramNode.set_default_shape(value) else: msg = "unknown node shape: %s" % value raise AttributeError(msg) def set_default_label_orientation(self, value): value = value.lower() if value in ('horizontal', 'vertical'): DiagramNode.label_orientation = value else: msg = "unknown label orientation: %s" % value raise AttributeError(msg) def set_default_text_color(self, color): warning("default_text_color is obsoleted; use default_textcolor") self.set_default_textcolor(color) def set_default_textcolor(self, color): self.textcolor = images.color_to_rgb(color) self._DiagramNode.set_default_text_color(self.textcolor) self._NodeGroup.set_default_text_color(self.textcolor) self._DiagramEdge.set_default_text_color(self.textcolor) def set_default_node_color(self, color): color = images.color_to_rgb(color) self._DiagramNode.set_default_color(color) def set_default_node_style(self, value): if re.search('^(?:none|solid|dotted|dashed|\d+(,\d+)*)$', value, re.I): self._DiagramNode.set_default_style(value) else: msg = "unknown node style: %s" % value raise AttributeError(msg) def set_default_line_color(self, color): warning("default_line_color is obsoleted; use default_linecolor") self.set_default_linecolor(color) def set_default_linecolor(self, color): self.linecolor = images.color_to_rgb(color) self._DiagramNode.set_default_linecolor(self.linecolor) self._DiagramEdge.set_default_color(self.linecolor) def set_default_group_color(self, color): color = images.color_to_rgb(color) self._NodeGroup.set_default_color(color) def set_shape_namespace(self, value): noderenderer.set_default_namespace(value) def set_default_fontfamily(self, fontfamily): self._DiagramNode.set_default_fontfamily(fontfamily) self._NodeGroup.set_default_fontfamily(fontfamily) self._DiagramEdge.set_default_fontfamily(fontfamily) def set_default_fontsize(self, fontsize): self._DiagramNode.set_default_fontsize(fontsize) self._NodeGroup.set_default_fontsize(fontsize) self._DiagramEdge.set_default_fontsize(fontsize) def set_shadow_style(self, value): value = value.lower() if value in ('solid', 'blur', 'none'): self.shadow_style = value else: msg = "unknown shadow style: %s" % value raise AttributeError(msg) def set_edge_layout(self, value): value = value.lower() if value in ('normal', 'flowchart'): warning("edge_layout is very experimental feature!") self.edge_layout = value else: msg = "unknown edge layout: %s" % value raise AttributeError(msg) def set_fontsize(self, value): warning("fontsize is obsoleted; use default_fontsize") self.set_default_fontsize(int(value)) blockdiag-1.5.3/src/blockdiag/imagedraw/0000755000076600000240000000000012556430040020705 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/imagedraw/__init__.py0000644000076600000240000000325312555656675023050 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import pkg_resources from blockdiag.utils.logging import warning drawers = {} def init_imagedrawers(debug=False): for drawer in pkg_resources.iter_entry_points('blockdiag_imagedrawers'): try: module = drawer.load() if hasattr(module, 'setup'): module.setup(module) except Exception as exc: if debug: warning('Failed to load %s: %r' % (drawer.module_name, exc)) def install_imagedrawer(ext, drawer): drawers[ext] = drawer def create(_format, filename, **kwargs): if len(drawers) == 0: init_imagedrawers(debug=kwargs.get('debug')) _format = _format.lower() if _format in drawers: drawer = drawers[_format](filename, **kwargs) else: msg = 'failed to load %s image driver' % _format raise RuntimeError(msg) if 'linejump' in kwargs.get('filters', []): from blockdiag.imagedraw.filters.linejump import LineJumpDrawFilter jumpsize = kwargs.get('jumpsize', 0) drawer = LineJumpDrawFilter(drawer, jumpsize) return drawer blockdiag-1.5.3/src/blockdiag/imagedraw/base.py0000644000076600000240000000372012363165364022205 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from functools import partial from blockdiag.imagedraw import textfolder from blockdiag.utils import Box class ImageDraw(object): self_generative_methods = [] nosideeffect_methods = ['set_canvas_size', 'textsize', 'textlinesize'] supported_path = False baseline_text_rendering = False def set_canvas_size(self, size): pass def set_options(self, **kwargs): pass def line(self, xy, **kwargs): pass def rectangle(self, box, **kwargs): pass def polygon(self, xy, **kwargs): pass def arc(self, xy, start, end, **kwargs): pass def ellipse(self, xy, **kwargs): pass def textsize(self, string, font, maxwidth=None, **kwargs): if maxwidth is None: maxwidth = 65535 box = Box(0, 0, maxwidth, 65535) textbox = self.textfolder(box, string, font, **kwargs) return textbox.outlinebox.size @property def textfolder(self): return partial(textfolder.get, self, adjustBaseline=self.baseline_text_rendering) def textlinesize(self, string, font, **kwargs): pass def text(self, xy, string, font, **kwargs): pass def textarea(self, box, string, font, **kwargs): pass def image(self, box, url): pass def save(self, filename, size, _format): pass blockdiag-1.5.3/src/blockdiag/imagedraw/filters/0000755000076600000240000000000012556430040022355 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/imagedraw/filters/__init__.py0000644000076600000240000000114412227701542024471 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. blockdiag-1.5.3/src/blockdiag/imagedraw/filters/linejump.py0000644000076600000240000001410612556277246024575 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import functools from blockdiag.utils import Box, XY class LazyReceiver(object): def __init__(self, target): self.target = target self.calls = [] def __getattr__(self, name): return self.get_lazy_method(name) def get_lazy_method(self, name): if name in self.target.nosideeffect_methods: method = self._find_method(name) return functools.partial(method, self.target) elif name in self.target.self_generative_methods: def _(*args, **kwargs): receiver = LazySubReceiver(name, self.target, *args, **kwargs) self.calls.append((receiver, args, kwargs)) return receiver return _ else: def _(*args, **kwargs): self.calls.append((name, args, kwargs)) return self return _ def _find_method(self, name): if isinstance(name, LazyReceiver): return name for p in self.target.__class__.__mro__: if name in p.__dict__: return p.__dict__[name] raise AttributeError("%s instance has no attribute '%s'" % (self.target.__class__.__name__, name)) def _run(self): for name, args, kwargs in self.calls: method = self._find_method(name) method(self.target, *args, **kwargs) class LazySubReceiver(LazyReceiver): def __init__(self, name, target, *args, **kwargs): self.name = name self.args = args self.kwargs = kwargs super(LazySubReceiver, self).__init__(target) def __call__(self, target, *args, **kwargs): method = self._find_method(self.name) self.target = method(self.target, *self.args, **self.kwargs) self._run() class LineJumpDrawFilter(LazyReceiver): def __init__(self, target, jump_radius): super(LineJumpDrawFilter, self).__init__(target) self.ytree = [] self.x_cross = {} self.y_cross = {} self.forward = 'holizonal' self.jump_radius = jump_radius self.jump_shift = 0 def set_options(self, **kwargs): if 'jump_forward' in kwargs: self.forward = kwargs['jump_forward'] if 'jump_radius' in kwargs: self.jump_radius = kwargs['jump_radius'] if 'jump_shift' in kwargs: self.jump_shift = kwargs['jump_shift'] def _run(self): for name, args, kwargs in self.calls: if name == 'line' and kwargs.get('jump'): ((x1, y1), (x2, y2)) = args[0] if self.forward == 'holizonal' and y1 == y2: self._holizonal_jumpline(x1, y1, x2, y2, **kwargs) continue elif self.forward == 'vertical' and x1 == x2: self._vertical_jumpline(x1, y1, x2, y2, **kwargs) continue method = self._find_method(name) method(self.target, *args, **kwargs) def _holizonal_jumpline(self, x1, y1, x2, y2, **kwargs): y = y1 if x2 < x1: x1, x2 = x2, x1 for x in sorted(self.x_cross.get(y, [])): if x1 < x and x < x2: arckwargs = dict(kwargs) del arckwargs['jump'] x += self.jump_shift r = self.jump_radius line = (XY(x1, y), XY(x - r, y)) self.target.line(line, **kwargs) box = Box(x - r, y - r, x + r, y + r) self.target.arc(box, 180, 0, **arckwargs) x1 = x + r self.target.line((XY(x1, y), XY(x2, y)), **kwargs) def _vertical_jumpline(self, x1, y1, x2, y2, **kwargs): x = x1 if y2 < y1: y1, y2 = y2, y1 for y in sorted(self.y_cross.get(x, [])): if y1 < y and y < y2: arckwargs = dict(kwargs) del arckwargs['jump'] y += self.jump_shift r = self.jump_radius line = (XY(x, y1), XY(x, y - r)) self.target.line(line, **kwargs) box = Box(x - r, y - r, x + r, y + r) self.target.arc(box, 270, 90, **arckwargs) y1 = y + r self.target.line((XY(x, y1), XY(x, y2)), **kwargs) def line(self, xy, **kwargs): from bisect import insort for st, ed in zip(xy[:-1], xy[1:]): self.get_lazy_method("line")((st, ed), **kwargs) if 'jump' in kwargs and kwargs['jump'] is True: if st.y == ed.y: # horizonal insort(self.ytree, (st.y, 0, (st, ed))) elif st.x == ed.x: # vertical insort(self.ytree, (max(st.y, ed.y), -1, (st, ed))) insort(self.ytree, (min(st.y, ed.y), +1, (st, ed))) def save(self, *args, **kwargs): # Search crosspoints from bisect import insort, bisect_left, bisect_right xtree = [] for y, _, ((x1, y1), (x2, y2)) in self.ytree: if x2 < x1: x1, x2 = x2, x1 if y2 < y1: y1, y2 = y2, y1 if y == y1: insort(xtree, x1) if y == y2: del xtree[bisect_left(xtree, x1)] for x in xtree[bisect_right(xtree, x1):bisect_left(xtree, x2)]: self.x_cross.setdefault(y, set()).add(x) self.y_cross.setdefault(x, set()).add(y) self._run() return self.target.save(*args, **kwargs) blockdiag-1.5.3/src/blockdiag/imagedraw/pdf.py0000644000076600000240000001762012362741345022046 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import math from reportlab.pdfgen import canvas from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib.utils import ImageReader from blockdiag.imagedraw import base from blockdiag.imagedraw.utils import memoize from blockdiag.utils import images, Box, Size from blockdiag.utils.fontmap import parse_fontpath from blockdiag.utils.compat import string_types class PDFImageDraw(base.ImageDraw): baseline_text_rendering = True def __init__(self, filename, **kwargs): self.filename = filename self.canvas = None self.fonts = {} self.set_canvas_size(Size(1, 1)) # This line make textsize() workable def set_canvas_size(self, size): self.canvas = canvas.Canvas(self.filename, pagesize=size) self.size = size def set_font(self, font): if font.path is None: msg = "Could not detect fonts, use --font opiton\n" raise RuntimeError(msg) if font.path not in self.fonts: path, index = parse_fontpath(font.path) if index: ttfont = TTFont(font.path, path, subfontIndex=index) else: ttfont = TTFont(font.path, path) pdfmetrics.registerFont(ttfont) self.fonts[font.path] = ttfont self.canvas.setFont(font.path, font.size) def set_render_params(self, **kwargs): self.set_stroke_color(kwargs.get('outline')) self.set_fill_color(kwargs.get('fill', 'none')) self.set_style(kwargs.get('style'), kwargs.get('thick')) params = {} if kwargs.get('fill', 'none') == 'none': params['fill'] = 0 else: params['fill'] = 1 if kwargs.get('outline') is None: params['stroke'] = 0 else: params['stroke'] = 1 return params def set_style(self, style, thick): if thick is None: thick = 1 if style == 'dotted': self.canvas.setDash([2 * thick, 2 * thick]) elif style == 'dashed': self.canvas.setDash([4 * thick, 4 * thick]) elif style == 'none': self.canvas.setDash([0, 65535 * thick]) elif re.search('^\d+(,\d+)*$', style or ""): self.canvas.setDash([int(n) * thick for n in style.split(',')]) else: self.canvas.setDash() def set_stroke_color(self, color="black"): if isinstance(color, string_types): self.canvas.setStrokeColor(color) elif color: rgb = (color[0] / 256.0, color[1] / 256.0, color[2] / 256.0) self.canvas.setStrokeColorRGB(*rgb) else: self.set_stroke_color() def set_fill_color(self, color="white"): if isinstance(color, string_types): if color != 'none': self.canvas.setFillColor(color) elif color: rgb = (color[0] / 256.0, color[1] / 256.0, color[2] / 256.0) self.canvas.setFillColorRGB(*rgb) else: self.set_fill_color() def path(self, pd, **kwargs): params = self.set_render_params(**kwargs) self.canvas.drawPath(pd, **params) def rectangle(self, box, **kwargs): x = box[0] y = self.size[1] - box[3] width = box[2] - box[0] height = box[3] - box[1] if 'thick' in kwargs and kwargs['thick'] is not None: self.canvas.setLineWidth(kwargs['thick']) params = self.set_render_params(**kwargs) self.canvas.rect(x, y, width, height, **params) if 'thick' in kwargs: self.canvas.setLineWidth(1) @memoize def textlinesize(self, string, font): self.set_font(font) width = self.canvas.stringWidth(string, font.path, font.size) return Size(int(math.ceil(width)), font.size) def text(self, xy, string, font, **kwargs): self.set_font(font) self.set_fill_color(kwargs.get('fill')) self.canvas.drawString(xy[0], self.size[1] - xy[1], string) def textarea(self, box, string, font, **kwargs): self.canvas.saveState() if 'rotate' in kwargs and kwargs['rotate'] != 0: angle = 360 - int(kwargs['rotate']) % 360 self.canvas.rotate(angle) if angle == 90: box = Box(-box.y2, box.x1, -box.y1, box.x1 + box.width) box = box.shift(x=self.size.height, y=self.size.height) elif angle == 180: box = Box(-box.x2, -box.y2, -box.x1, -box.y2 + box.height) box = box.shift(y=self.size.height * 2) elif angle == 270: box = Box(box.y1, -box.x2, box.y2, -box.x1) box = box.shift(x=-self.size.height, y=self.size.height) self.set_font(font) lines = self.textfolder(box, string, font, **kwargs) if kwargs.get('outline'): outline = kwargs.get('outline') self.rectangle(lines.outlinebox, fill='white', outline=outline) rendered = False for string, xy in lines.lines: self.text(xy, string, font, **kwargs) rendered = True self.canvas.restoreState() if not rendered and font.size > 0: font.size = int(font.size * 0.8) self.textarea(box, string, font, **kwargs) def line(self, xy, **kwargs): self.set_stroke_color(kwargs.get('fill', 'none')) self.set_style(kwargs.get('style'), kwargs.get('thick')) if 'thick' in kwargs and kwargs['thick'] is not None: self.canvas.setLineWidth(kwargs['thick']) p1 = xy[0] y = self.size[1] for p2 in xy[1:]: self.canvas.line(p1.x, y - p1.y, p2.x, y - p2.y) p1 = p2 if 'thick' in kwargs: self.canvas.setLineWidth(1) def arc(self, xy, start, end, **kwargs): start, end = 360 - end, 360 - start r = (360 + end - start) % 360 self.set_render_params(**kwargs) y = self.size[1] self.canvas.arc(xy[0], y - xy[3], xy[2], y - xy[1], start, r) def ellipse(self, xy, **kwargs): params = self.set_render_params(**kwargs) y = self.size[1] self.canvas.ellipse(xy[0], y - xy[3], xy[2], y - xy[1], **params) def polygon(self, xy, **kwargs): pd = self.canvas.beginPath() y = self.size[1] pd.moveTo(xy[0][0], y - xy[0][1]) for p in xy[1:]: pd.lineTo(p[0], y - p[1]) params = self.set_render_params(**kwargs) self.canvas.drawPath(pd, **params) def image(self, box, url): try: image = images.open(url, mode='pillow') if image.mode not in ('RGBA', 'L', 'RGB', 'CYMYK'): # convert to format that reportlab can recognize image = image.convert('RGBA') y = self.size[1] - box[3] data = ImageReader(image) self.canvas.drawImage(data, box.x1, y, box.width, box.height, mask='auto', preserveAspectRatio=True) except IOError: pass def save(self, filename, size, _format): # Ignore size and format parameter; compatibility for ImageDrawEx. self.canvas.showPage() self.canvas.save() def setup(self): from blockdiag.imagedraw import install_imagedrawer install_imagedrawer('pdf', PDFImageDraw) blockdiag-1.5.3/src/blockdiag/imagedraw/png.py0000644000076600000240000003442212366150234022053 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import re import math from itertools import tee try: from future_builtins import zip except ImportError: pass from functools import partial, wraps from PIL import Image, ImageDraw, ImageFont, ImageFilter from blockdiag.imagedraw import base from blockdiag.imagedraw.utils import memoize from blockdiag.imagedraw.utils.ellipse import dots as ellipse_dots from blockdiag.utils import images, Box, Size, XY from blockdiag.utils.fontmap import parse_fontpath, FontMap from blockdiag.utils.myitertools import istep, stepslice # apply monkey patch to pillow from blockdiag.imagedraw.utils import pillow pillow.apply_patch() def point_pairs(xylist): iterable = iter(xylist) for pt in iterable: if isinstance(pt, int): yield (pt, next(iterable)) else: yield pt def line_segments(xylist): p1, p2 = tee(point_pairs(xylist)) next(p2) return zip(p1, p2) def dashize_line(line, length): pt1, pt2 = line if pt1[0] == pt2[0]: # holizonal if pt1[1] > pt2[1]: pt2, pt1 = line r = stepslice(range(pt1[1], pt2[1]), length) for y1, y2 in istep(n for n in r): yield [(pt1[0], y1), (pt1[0], y2)] elif pt1[1] == pt2[1]: # vertical if pt1[0] > pt2[0]: pt2, pt1 = line r = stepslice(range(pt1[0], pt2[0]), length) for x1, x2 in istep(n for n in r): yield [(x1, pt1[1]), (x2, pt1[1])] else: # diagonal if pt1[0] > pt2[0]: pt2, pt1 = line # DDA (Digital Differential Analyzer) Algorithm locus = [] m = float(pt2[1] - pt1[1]) / float(pt2[0] - pt1[0]) x = pt1[0] y = pt1[1] while x <= pt2[0]: locus.append((int(x), int(round(y)))) x += 1 y += m for p1, p2 in istep(stepslice(locus, length)): yield (p1, p2) def style2cycle(style, thick): if thick is None: thick = 1 if style == 'dotted': length = [2 * thick, 2 * thick] elif style == 'dashed': length = [4 * thick, 4 * thick] elif style == 'none': length = [0, 65535 * thick] elif re.search('^\d+(,\d+)*$', style or ""): length = [int(n) * thick for n in style.split(',')] else: length = None return length def ttfont_for(font): if font.path: path, index = parse_fontpath(font.path) if index: ttfont = ImageFont.truetype(path, font.size, index=index) else: ttfont = ImageFont.truetype(path, font.size) else: ttfont = None return ttfont class ImageDrawExBase(base.ImageDraw): def __init__(self, filename, **kwargs): self.filename = filename self.transparency = kwargs.get('transparency') self.bgcolor = kwargs.get('color', (256, 256, 256)) self._image = None self.draw = None if kwargs.get('parent'): self.scale_ratio = kwargs.get('parent').scale_ratio else: self.scale_ratio = kwargs.get('scale_ratio', 1) self.set_canvas_size(Size(1, 1)) # This line make textsize() workable def paste(self, image, pt, mask=None): self._image.paste(image, pt, mask) self.draw = ImageDraw.Draw(self._image) def set_canvas_size(self, size): if self.transparency: mode = 'RGBA' else: mode = 'RGB' self._image = Image.new(mode, size, self.bgcolor) # set transparency to background if self.transparency: alpha = Image.new('L', size, 1) self._image.putalpha(alpha) self.draw = ImageDraw.Draw(self._image) def resizeCanvas(self, size): self._image = self._image.resize(size, Image.ANTIALIAS) self.draw = ImageDraw.Draw(self._image) def arc(self, box, start, end, **kwargs): style = kwargs.get('style') if 'style' in kwargs: del kwargs['style'] if 'thick' in kwargs: del kwargs['thick'] if style: while start > end: end += 360 cycle = style2cycle(style, kwargs.get('width')) for pt in ellipse_dots(box, cycle, start, end): self.draw.line([pt, pt], fill=kwargs['fill']) else: self.draw.arc(box.to_integer_point(), start, end, **kwargs) def ellipse(self, box, **kwargs): if 'filter' in kwargs: del kwargs['filter'] style = kwargs.get('style') if 'style' in kwargs: del kwargs['style'] if style: if kwargs.get('fill') != 'none': kwargs2 = dict(kwargs) if 'outline' in kwargs2: del kwargs2['outline'] self.draw.ellipse(box, **kwargs2) if 'outline' in kwargs: kwargs['fill'] = kwargs['outline'] del kwargs['outline'] cycle = style2cycle(style, kwargs.get('width')) for pt in ellipse_dots(box, cycle): self.draw.line([pt, pt], fill=kwargs['fill']) else: if kwargs.get('fill') == 'none': del kwargs['fill'] self.draw.ellipse(box.to_integer_point(), **kwargs) def line(self, xy, **kwargs): if 'jump' in kwargs: del kwargs['jump'] if 'thick' in kwargs: if kwargs['thick'] is not None: kwargs['width'] = kwargs['thick'] del kwargs['thick'] style = kwargs.get('style') if kwargs.get('fill') == 'none': pass elif (style in ('dotted', 'dashed', 'none') or re.search('^\d+(,\d+)*$', style or "")): self.dashed_line(xy, **kwargs) else: if 'style' in kwargs: del kwargs['style'] self.draw.line(xy, **kwargs) def dashed_line(self, xy, **kwargs): style = kwargs.get('style') del kwargs['style'] cycle = style2cycle(style, kwargs.get('width')) for line in line_segments(xy): for subline in dashize_line(line, cycle): self.line(subline, **kwargs) def rectangle(self, box, **kwargs): thick = kwargs.get('thick', self.scale_ratio) fill = kwargs.get('fill') outline = kwargs.get('outline') style = kwargs.get('style') if thick == 1: d = 0 else: d = int(math.ceil(thick / 2.0)) if fill and fill != 'none': self.draw.rectangle(box, fill=fill) x1, y1, x2, y2 = box lines = (((x1, y1), (x2, y1)), ((x1, y2), (x2, y2)), # horizonal ((x1, y1 - d), (x1, y2 + d)), # vettical (left) ((x2, y1 - d), (x2, y2 + d))) # vertical (right) for line in lines: self.line(line, fill=outline, width=thick, style=style) def polygon(self, xy, **kwargs): if 'filter' in kwargs: del kwargs['filter'] if kwargs.get('fill') != 'none': kwargs2 = dict(kwargs) if 'style' in kwargs2: del kwargs2['style'] if 'outline' in kwargs2: del kwargs2['outline'] self.draw.polygon(xy, **kwargs2) if kwargs.get('outline'): kwargs['fill'] = kwargs['outline'] del kwargs['outline'] self.line(xy, **kwargs) @property def textfolder(self): textfolder = super(ImageDrawExBase, self).textfolder return partial(textfolder, scale=self.scale_ratio) @memoize def textlinesize(self, string, font): ttfont = ttfont_for(font) if ttfont is None: size = self.draw.textsize(string, font=None) font_ratio = font.size * 1.0 / FontMap.BASE_FONTSIZE size = Size(int(size[0] * font_ratio), int(size[1] * font_ratio)) else: size = Size(*ttfont.getsize(string)) return size def text(self, xy, string, font, **kwargs): fill = kwargs.get('fill') ttfont = ttfont_for(font) if ttfont is None: if self.scale_ratio == 1 and font.size == FontMap.BASE_FONTSIZE: self.draw.text(xy, string, fill=fill) else: size = self.draw.textsize(string) image = Image.new('RGBA', size) draw = ImageDraw.Draw(image) draw.text((0, 0), string, fill=fill) del draw basesize = (size[0] * self.scale_ratio, size[1] * self.scale_ratio) text_image = image.resize(basesize, Image.ANTIALIAS) self.paste(text_image, xy, text_image) else: size = ttfont.getsize(string) # Generate mask to support BDF(bitmap font) mask = Image.new('1', size) draw = ImageDraw.Draw(mask) draw.text((0, 0), string, fill='white', font=ttfont) # Rendering text filler = Image.new('RGB', size, fill) self.paste(filler, xy, mask) def textarea(self, box, string, font, **kwargs): if 'rotate' in kwargs and kwargs['rotate'] != 0: angle = 360 - int(kwargs['rotate']) % 360 del kwargs['rotate'] if angle in (90, 270): _box = Box(0, 0, box.height, box.width) else: _box = box text = ImageDrawEx(None, parent=self, transparency=True) text.set_canvas_size(_box.size) textbox = Box(0, 0, _box.width, _box.height) text.textarea(textbox, string, font, **kwargs) filler = Image.new('RGB', box.size, kwargs.get('fill')) self.paste(filler, box.topleft, text._image.rotate(angle)) return lines = self.textfolder(box, string, font, **kwargs) if kwargs.get('outline'): outline = kwargs.get('outline') self.rectangle(lines.outlinebox, fill='white', outline=outline) rendered = False for string, xy in lines.lines: self.text(xy, string, font, **kwargs) rendered = True if not rendered and font.size > 0: _font = font.duplicate() _font.size = int(font.size * 0.8) self.textarea(box, string, _font, **kwargs) def image(self, box, url): try: image = images.open(url, mode='pillow') # resize image. w = min([box.width, image.size[0] * self.scale_ratio]) h = min([box.height, image.size[1] * self.scale_ratio]) image.thumbnail((w, h), Image.ANTIALIAS) # centering image. w, h = image.size if box.width > w: x = box[0] + (box.width - w) // 2 else: x = box[0] if box.height > h: y = box[1] + (box.height - h) // 2 else: y = box[1] if image.mode == 'P': # convert P to RGBA to masking transparent pixels image = image.convert('RGBA') if image.mode == 'RGBA': self.paste(image, (x, y), mask=image) else: self.paste(image, (x, y)) except IOError: pass def save(self, filename, size, _format): if filename: self.filename = filename if size is None: x = int(self._image.size[0] / self.scale_ratio) y = int(self._image.size[1] / self.scale_ratio) size = (x, y) self._image.thumbnail(size, Image.ANTIALIAS) if self.filename: self._image.save(self.filename, _format) image = None else: from io import BytesIO tmp = BytesIO() self._image.save(tmp, _format) image = tmp.getvalue() return image def blurred(fn): PADDING = 16 def get_shape_box(*args): if fn.__name__ == 'polygon': xlist = [pt.x for pt in args[0]] ylist = [pt.y for pt in args[0]] return Box(min(xlist), min(ylist), max(xlist), max(ylist)) else: return args[0] def get_abs_coordinate(box, *args): dx = box.x1 - PADDING dy = box.y1 - PADDING if fn.__name__ == 'polygon': return [pt.shift(-dx, -dy) for pt in args[0]] else: return box.shift(-dx, -dy) def create_shadow(self, size, *args, **kwargs): drawer = ImageDrawExBase(self.filename, transparency=True) drawer.set_canvas_size(size) getattr(drawer, fn.__name__)(*args, **kwargs) for _ in range(15): drawer._image = drawer._image.filter(ImageFilter.SMOOTH_MORE) return drawer._image @wraps(fn) def func(self, *args, **kwargs): args = list(args) if kwargs.get('filter') not in ('blur', 'transp-blur'): return fn(self, *args, **kwargs) else: box = get_shape_box(*args) args[0] = get_abs_coordinate(box, *args) size = Size(box.width + PADDING * 2, box.height + PADDING * 2) shadow = create_shadow(self, size, *args, **kwargs) xy = XY(box.x1 - PADDING, box.y1 - PADDING) self.paste(shadow, xy, shadow) return func class ImageDrawEx(ImageDrawExBase): @blurred def ellipse(self, box, **kwargs): super(ImageDrawEx, self).ellipse(box, **kwargs) @blurred def rectangle(self, box, **kwargs): super(ImageDrawEx, self).rectangle(box, **kwargs) @blurred def polygon(self, xy, **kwargs): super(ImageDrawEx, self).polygon(xy, **kwargs) def setup(self): from blockdiag.imagedraw import install_imagedrawer install_imagedrawer('png', ImageDrawEx) blockdiag-1.5.3/src/blockdiag/imagedraw/simplesvg.py0000644000076600000240000001533112344547621023304 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re from blockdiag.utils.compat import u, string_types from io import StringIO def _escape(s): if not isinstance(s, string_types): s = str(s) return s.replace("&", "&").replace("<", "<").replace(">", ">") def _quote(s): return '"%s"' % _escape(s).replace('"', """) class base(object): def __init__(self, *args, **kwargs): self.text = None self.elements = [] self.attributes = {} for key, value in kwargs.items(): self.add_attribute(key, value) def add_attribute(self, key, value): setter = 'set_%s' % key if hasattr(self, setter): getattr(self, setter)(value) else: key = re.sub('_', '-', key) self.attributes[key] = value def addElement(self, element): self.elements.append(element) def set_text(self, text): self.text = text def to_xml(self, io, level=0): clsname = self.__class__.__name__ indent = ' ' * level io.write(u('%s<%s') % (indent, clsname)) for key in sorted(self.attributes): value = self.attributes[key] if value is not None: io.write(u(' %s=%s') % (_escape(key), _quote(value))) if self.elements == []: if self.text is not None: io.write(u(">%s\n") % (_escape(self.text), clsname)) else: io.write(u(" />\n")) elif self.elements: if self.text is not None: io.write(u(">%s\n") % (_escape(self.text),)) else: io.write(u(">\n")) for e in self.elements: e.to_xml(io, level + 1) io.write(u('%s\n') % (indent, clsname)) class element(base): def __init__(self, x, y, width=None, height=None, *args, **kwargs): super(element, self).__init__(*args, **kwargs) self.attributes['x'] = x self.attributes['y'] = y if width is not None: self.attributes['width'] = width if height is not None: self.attributes['height'] = height class svg(base): def __init__(self, x, y, width, height, **kwargs): if kwargs.get('noviewbox'): super(svg, self).__init__(width=(width - x), height=(height - y)) else: viewbox = "%d %d %d %d" % (x, y, width, height) super(svg, self).__init__(viewBox=viewbox) self.nodoctype = kwargs.get('nodoctype', False) self.add_attribute('xmlns', 'http://www.w3.org/2000/svg') def to_xml(self): io = StringIO() if not self.nodoctype: url = "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" io.write(u("\n")) io.write(u('\n') % url) super(svg, self).to_xml(io) return io.getvalue() class title(base): def __init__(self, _title): super(title, self).__init__(text=_title) class desc(base): def __init__(self, _title): super(desc, self).__init__(text=_title) class text(element): def __init__(self, x, y, _text, **kwargs): super(text, self).__init__(x, y, text=_text, **kwargs) class rect(element): pass class ellipse(base): def __init__(self, cx, cy, rx, ry, **kwargs): super(ellipse, self).__init__(cx=cx, cy=cy, rx=rx, ry=ry, **kwargs) class image(element): def __init__(self, uri, x, y, width, height, **kwargs): super(image, self).__init__(x, y, width, height, **kwargs) self.add_attribute('xlink:href', uri) class polygon(base): def __init__(self, points, **kwargs): xylist = " ".join('%d,%d' % pt for pt in points) super(polygon, self).__init__(points=xylist, **kwargs) class path(base): def __init__(self, data, **kwargs): super(path, self).__init__(d=data, **kwargs) class pathdata: def __init__(self, x=None, y=None): self.path = [] if x is not None and y is not None: self.move(x, y) def closepath(self): self.path.append('z') def move(self, x, y): self.path.append('M %s %s' % (x, y)) def relmove(self, x, y): self.path.append('m %s %s' % (x, y)) def line(self, x, y): self.path.append('L %s %s' % (x, y)) def relline(self, x, y): self.path.append('l %s %s' % (x, y)) def hline(self, x): self.path.append('H%s' % (x,)) def relhline(self, x): self.path.append('h%s' % (x,)) def vline(self, y): self.path.append('V%s' % (y,)) def relvline(self, y): self.path.append('v%s' % (y,)) def bezier(self, x1, y1, x2, y2, x, y): self.path.append('C%s,%s %s,%s %s,%s' % (x1, y1, x2, y2, x, y)) def relbezier(self, x1, y1, x2, y2, x, y): self.path.append('c%s,%s %s,%s %s,%s' % (x1, y1, x2, y2, x, y)) def smbezier(self, x2, y2, x, y): self.path.append('S%s,%s %s,%s' % (x2, y2, x, y)) def relsmbezier(self, x2, y2, x, y): self.path.append('s%s,%s %s,%s' % (x2, y2, x, y)) def qbezier(self, x1, y1, x, y): self.path.append('Q%s,%s %s,%s' % (x1, y1, x, y)) def qrelbezier(self, x1, y1, x, y): self.path.append('q%s,%s %s,%s' % (x1, y1, x, y)) def smqbezier(self, x, y): self.path.append('T%s %s' % (x, y)) def relsmqbezier(self, x, y): self.path.append('t%s %s' % (x, y)) def ellarc(self, rx, ry, xrot, laf, sf, x, y): self.path.append('A%s,%s %s %s %s %s %s' % (rx, ry, xrot, laf, sf, x, y)) def relellarc(self, rx, ry, xrot, laf, sf, x, y): self.path.append('a%s,%s %s %s %s %s %s' % (rx, ry, xrot, laf, sf, x, y)) def __repr__(self): return ' '.join(self.path) class defs(base): pass class g(base): pass class a(base): pass class filter(element): def __init__(self, x, y, width, height, **kwargs): super(filter, self).__init__(x, y, width, height, **kwargs) def svgclass(name): """ svg class generating function """ return type(name, (base,), {}) blockdiag-1.5.3/src/blockdiag/imagedraw/svg.py0000644000076600000240000002372712423611711022070 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import re from PIL.Image import Image from base64 import b64encode from blockdiag.imagedraw import base as _base from blockdiag.imagedraw.simplesvg import ( svg, svgclass, filter, title, desc, defs, g, a, text, rect, polygon, ellipse, path, pathdata, image ) from blockdiag.imagedraw.utils import memoize from blockdiag.imagedraw.utils.ellipse import endpoints as ellipse_endpoints from blockdiag.utils import images, Box, XY, is_Pillow_available feGaussianBlur = svgclass('feGaussianBlur') def rgb(color): if isinstance(color, tuple): color = 'rgb(%d,%d,%d)' % color return color def style(name): if name == 'blur': value = "filter:url(#filter_blur)" elif name == 'transp-blur': value = "filter:url(#filter_blur);opacity:0.7;fill-opacity:1" else: value = None return value def dasharray(pattern, thick): if thick is None: thick = 1 if pattern == 'dotted': value = 2 * thick elif pattern == 'dashed': value = 4 * thick elif pattern == 'none': value = "%d %d" % (0, 65535 * thick) elif re.search('^\d+(,\d+)*$', pattern or ""): l = [int(n) * thick for n in pattern.split(",")] value = " ".join(str(n) for n in l) else: value = None return value def drawing_params(kwargs): params = {} if 'style' in kwargs: params['stroke_dasharray'] = dasharray(kwargs.get('style'), kwargs.get('thick')) if 'filter' in kwargs: params['style'] = style(kwargs.get('filter')) return params class SVGImageDrawElement(_base.ImageDraw): self_generative_methods = ['group', 'anchor'] supported_path = True baseline_text_rendering = True def __init__(self, svg, parent=None): self.svg = svg def path(self, pd, **kwargs): fill = kwargs.get('fill') outline = kwargs.get('outline') p = path(pd, fill=rgb(fill), stroke=rgb(outline), **drawing_params(kwargs)) self.svg.addElement(p) def rectangle(self, box, **kwargs): thick = kwargs.get('thick') fill = kwargs.get('fill', 'none') outline = kwargs.get('outline') r = rect(box.x, box.y, box.width, box.height, fill=rgb(fill), stroke=rgb(outline), stroke_width=thick, **drawing_params(kwargs)) self.svg.addElement(r) @memoize def textlinesize(self, string, font, **kwargs): if is_Pillow_available(): if not hasattr(self, '_pil_drawer'): from blockdiag.imagedraw import png self._pil_drawer = png.ImageDrawEx(None) return self._pil_drawer.textlinesize(string, font) else: from blockdiag.imagedraw.utils import textsize return textsize(string, font) def text(self, point, string, font, **kwargs): fill = kwargs.get('fill') size = self.textlinesize(string, font) point = point.shift(size.width / 2) t = text(point.x, point.y, string, fill=rgb(fill), font_family=font.generic_family, font_size=font.size, font_weight=font.weight, font_style=font.style, text_anchor='middle', textLength=size.width) self.svg.addElement(t) def textarea(self, box, string, font, **kwargs): if 'rotate' in kwargs and kwargs['rotate'] != 0: self.rotated_textarea(box, string, font, **kwargs) else: lines = self.textfolder(box, string, font, **kwargs) if kwargs.get('outline'): outline = kwargs.get('outline') self.rectangle(lines.outlinebox, fill='white', outline=outline) rendered = False for string, point in lines.lines: self.text(point, string, font, **kwargs) rendered = True if not rendered and font.size > 0: _font = font.duplicate() _font.size = int(font.size * 0.8) self.textarea(box, string, _font, **kwargs) def rotated_textarea(self, box, string, font, **kwargs): angle = int(kwargs['rotate']) % 360 del kwargs['rotate'] if angle in (90, 270): _box = Box(box[0], box[1], box[0] + box.height, box[1] + box.width) if angle == 90: _box = _box.shift(x=box.width) elif angle == 270: _box = _box.shift(y=box.height) elif angle == 180: _box = Box(box[2], box[3], box[2] + box.width, box[3] + box.height) else: _box = Box(box[0], box[1], box[0] + box.width, box[1] + box.height) rotate = "rotate(%d,%d,%d)" % (angle, _box[0], _box[1]) group = g(transform="%s" % rotate) self.svg.addElement(group) elem = SVGImageDrawElement(group, self) elem.textarea(_box, string, font, **kwargs) def line(self, points, **kwargs): fill = kwargs.get('fill') thick = kwargs.get('thick') pd = pathdata(points[0].x, points[0].y) for pt in points[1:]: pd.line(pt.x, pt.y) p = path(pd, fill="none", stroke=rgb(fill), stroke_width=thick, **drawing_params(kwargs)) self.svg.addElement(p) def arc(self, box, start, end, **kwargs): fill = kwargs.get('fill') w = box.width / 2 h = box.height / 2 if start > end: end += 360 endpoints = ellipse_endpoints(1, w, h, start, end) pt1 = XY(box.x + w + round(endpoints[0].x, 0), box.y + h + round(endpoints[0].y, 0)) pt2 = XY(box.x + w + round(endpoints[1].x, 0), box.y + h + round(endpoints[1].y, 0)) if end - start > 180: largearc = 1 else: largearc = 0 pd = pathdata(pt1[0], pt1[1]) pd.ellarc(w, h, 0, largearc, 1, pt2[0], pt2[1]) p = path(pd, fill="none", stroke=rgb(fill), **drawing_params(kwargs)) self.svg.addElement(p) def ellipse(self, box, **kwargs): fill = kwargs.get('fill') outline = kwargs.get('outline') w = box.width / 2 h = box.height / 2 pt = box.center e = ellipse(pt.x, pt.y, w, h, fill=rgb(fill), stroke=rgb(outline), **drawing_params(kwargs)) self.svg.addElement(e) def polygon(self, points, **kwargs): fill = kwargs.get('fill') outline = kwargs.get('outline') pg = polygon(points, fill=rgb(fill), stroke=rgb(outline), **drawing_params(kwargs)) self.svg.addElement(pg) def image(self, box, url): if hasattr(url, 'read'): url = "data:;base64," + str(b64encode(url.read())) else: if isinstance(url, Image): ext = None else: ext = os.path.splitext(url)[1].lower() if ext not in ('.jpg', '.png', '.gif'): stream = None try: stream = images.open(url, mode='png') url = "data:;base64," + str(b64encode(stream.read())) except IOError: pass finally: if stream: stream.close() im = image(url, box.x1, box.y1, box.width, box.height) self.svg.addElement(im) def anchor(self, url): a_node = a(url) a_node.add_attribute('xlink:href', url) self.svg.addElement(a_node) return SVGImageDrawElement(a_node, self) def group(self): group = g() self.svg.addElement(group) return SVGImageDrawElement(group, self) class SVGImageDraw(SVGImageDrawElement): def __init__(self, filename, **kwargs): super(SVGImageDraw, self).__init__(None) self.filename = filename self.options = kwargs self.set_canvas_size((0, 0)) def set_canvas_size(self, size): self.svg = svg(0, 0, size[0], size[1], **self.options) uri = 'http://www.inkscape.org/namespaces/inkscape' self.svg.add_attribute('xmlns:inkspace', uri) uri = 'http://www.w3.org/1999/xlink' self.svg.add_attribute('xmlns:xlink', uri) # inkspace's Gaussian filter if self.options.get('style') != 'blur': fgb = feGaussianBlur(id='feGaussianBlur3780', stdDeviation=4.2) fgb.add_attribute('inkspace:collect', 'always') f = filter(-0.07875, -0.252, 1.1575, 1.504, id='filter_blur') f.add_attribute('inkspace:collect', 'always') f.addElement(fgb) d = defs(id='defs_block') d.addElement(f) self.svg.addElement(d) self.svg.addElement(title('blockdiag')) self.svg.addElement(desc(self.options.get('code'))) def save(self, filename, size, _format): # Ignore format parameter; compatibility for ImageDrawEx. if filename: self.filename = filename if size: self.svg.attributes['width'] = size[0] self.svg.attributes['height'] = size[1] image = self.svg.to_xml() if self.filename: open(self.filename, 'wb').write(image.encode('utf-8')) return image def setup(self): from blockdiag.imagedraw import install_imagedrawer install_imagedrawer('svg', SVGImageDraw) blockdiag-1.5.3/src/blockdiag/imagedraw/textfolder.py0000644000076600000240000002336412234223041023441 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re from blockdiag.utils import Box, Size, XY from blockdiag.utils.compat import u, string_types def splitlabel(string): """Split text to lines as generator. Every line will be stripped. If text includes characters "\n", treat as line separator. """ string = re.sub('^\s*', '', string) string = re.sub('\s*$', '', string) string = re.sub('\xa5', '\\\\', string) string = re.sub('(\\\\){2}', '\x00', string) string = re.sub('\\\\n', '\n', string) for line in string.splitlines(): yield re.sub('\x00', '\\\\', line).strip() def splittext(metrics, text, bound, measure='width'): folded = [] if text == '': folded.append(u(' ')) for i in range(len(text), 0, -1): textsize = metrics.textsize(text[0:i]) if getattr(textsize, measure) <= bound: folded.append(text[0:i]) if text[i:]: folded += splittext(metrics, text[i:], bound, measure) break return folded def truncate_text(metrics, text, bound, measure='width'): for i in range(len(text), 0, -1): textsize = metrics.textsize(text[0:i] + ' ...') if getattr(textsize, measure) <= bound: return text[0:i] + ' ...' return text def get(*args, **kwargs): if kwargs.get('orientation') == 'vertical': return VerticalTextFolder(*args, **kwargs) else: return HorizontalTextFolder(*args, **kwargs) class VerticalTextFolder(object): def __init__(self, drawer, box, string, font, **kwargs): self.drawer = drawer self.box = box self.string = string self.font = font self.scale = 1 self.halign = kwargs.get('halign', 'center') self.valign = kwargs.get('valign', 'center') self.padding = kwargs.get('padding', 8) self.line_spacing = kwargs.get('line_spacing', 2) if kwargs.get('adjustBaseline'): self.adjustBaseline = True else: self.adjustBaseline = False self._result = self._lines() def textsize(self, text, scaled=False): if isinstance(text, string_types): size = [self.drawer.textlinesize(c, self.font) for c in text] width = max(s.width for s in size) height = (sum(s.height for s in size) + self.line_spacing * (len(text) - 1)) textsize = Size(width, height) else: if text: size = [self.textsize(s) for s in text] height = max(s.height for s in size) width = (sum(s.width for s in size) + self.line_spacing * (len(text) - 1)) textsize = Size(width, height) else: textsize = Size(0, 0) if scaled: textsize = Size(textsize.width * self.scale, textsize.height * self.scale) return textsize @property def lines(self): textsize = self.textsize(self._result, scaled=True) dx, _ = self.box.get_padding_for(textsize, halign=self.halign, padding=self.padding) width = self.box.width - dx + self.line_spacing base_xy = XY(self.box.x1, self.box.y1) for string in self._result: textsize = self.textsize(string, scaled=True) _, dy = self.box.get_padding_for(textsize, valign=self.valign, padding=self.line_spacing) height = dy width -= textsize.width + self.line_spacing for char in string: charsize = self.textsize(char, scaled=True) if self.adjustBaseline: draw_xy = base_xy.shift(width, height + charsize.height) else: draw_xy = base_xy.shift(width, height) yield char, draw_xy height += charsize.height + self.line_spacing @property def outlinebox(self): corners = [] for string, xy in self.lines: textsize = self.textsize(string) width = textsize[0] * self.scale height = textsize[1] * self.scale if self.adjustBaseline: xy = XY(xy.x, xy.y - textsize[1]) corners.append(xy) corners.append(XY(xy.x + width, xy.y + height)) if corners: box = Box(min(p.x for p in corners) - self.padding, min(p.y for p in corners) - self.line_spacing, max(p.x for p in corners) + self.padding, max(p.y for p in corners) + self.line_spacing) else: box = Box(self.box[0], self.box[1], self.box[0], self.box[1]) return box def _lines(self): lines = [] measure = 'height' maxwidth, maxheight = self.box.size width = 0 finished = False for line in splitlabel(self.string): for folded in splittext(self, line, maxheight, measure): textsize = self.textsize(folded) if width + textsize.width + self.line_spacing < maxwidth: lines.append(folded) width += textsize.width + self.line_spacing elif len(lines) > 0: lines[-1] = truncate_text(self, lines[-1], maxheight, measure) finished = True break if finished: break return lines class HorizontalTextFolder(object): def __init__(self, drawer, box, string, font, **kwargs): self.drawer = drawer self.box = box self.string = string self.font = font self.scale = 1 self.halign = kwargs.get('halign', 'center') self.valign = kwargs.get('valign', 'center') self.padding = kwargs.get('padding', 8) self.line_spacing = kwargs.get('line_spacing', 2) if kwargs.get('adjustBaseline'): self.adjustBaseline = True else: self.adjustBaseline = False self._result = self._lines() def textsize(self, text, scaled=False): if isinstance(text, string_types): textsize = self.drawer.textlinesize(text, self.font) else: if text: size = [self.textsize(s) for s in text] width = max(s.width for s in size) height = (sum(s.height for s in size) + self.line_spacing * (len(text) - 1)) textsize = Size(width, height) else: textsize = Size(0, 0) if scaled: textsize = Size(textsize.width * self.scale, textsize.height * self.scale) return textsize @property def lines(self): textsize = self.textsize(self._result, scaled=True) _, dy = self.box.get_padding_for(textsize, valign=self.valign, padding=self.line_spacing) height = dy base_xy = XY(self.box.x1, self.box.y1) for string in self._result: textsize = self.textsize(string, scaled=True) dx, _ = self.box.get_padding_for(textsize, halign=self.halign, padding=self.padding) if self.adjustBaseline: draw_xy = base_xy.shift(dx, height + textsize.height) else: draw_xy = base_xy.shift(dx, height) yield string, draw_xy height += textsize.height + self.line_spacing @property def outlinebox(self): corners = [] for string, xy in self.lines: textsize = self.textsize(string) width = textsize[0] * self.scale height = textsize[1] * self.scale if self.adjustBaseline: xy = XY(xy.x, xy.y - textsize[1]) corners.append(xy) corners.append(XY(xy.x + width, xy.y + height)) if corners: box = Box(min(p.x for p in corners) - self.padding, min(p.y for p in corners) - self.line_spacing, max(p.x for p in corners) + self.padding, max(p.y for p in corners) + self.line_spacing) else: box = Box(self.box[0], self.box[1], self.box[0], self.box[1]) return box def _lines(self): lines = [] measure = 'width' maxwidth, maxheight = self.box.size height = 0 finished = False for line in splitlabel(self.string): for folded in splittext(self, line, maxwidth, measure): textsize = self.textsize(folded) if height + textsize.height + self.line_spacing < maxheight: lines.append(folded) height += textsize.height + self.line_spacing else: if len(lines) > 0: lines[-1] = truncate_text(self, lines[-1], maxwidth, measure) finished = True break if finished: break return lines blockdiag-1.5.3/src/blockdiag/imagedraw/utils/0000755000076600000240000000000012556430040022045 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/imagedraw/utils/__init__.py0000644000076600000240000000410212362741321024155 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import math import unicodedata from functools import wraps from blockdiag.utils import Size from blockdiag.utils.compat import u def is_zenkaku(char): """Detect given character is Japanese ZENKAKU character""" char_width = unicodedata.east_asian_width(char) return char_width in u("WFA") def zenkaku_len(string): """Count Japanese ZENKAKU characters from string""" return len([x for x in string if is_zenkaku(x)]) def hankaku_len(string): """Count non Japanese ZENKAKU characters from string""" return len([x for x in string if not is_zenkaku(x)]) def string_width(string): """Measure rendering width of string. Count ZENKAKU-character as 2-point and non ZENKAKU-character as 1-point """ widthmap = {'Na': 1, 'N': 1, 'H': 1, 'W': 2, 'F': 2, 'A': 2} return sum(widthmap[unicodedata.east_asian_width(c)] for c in string) def textsize(string, font): """Measure rendering size (width and height) of line. Returned size will not be exactly as rendered text size, Because this method does not use fonts to measure size. """ width = (zenkaku_len(string) * font.size + hankaku_len(string) * font.size * 0.55) return Size(int(math.ceil(width)), font.size) def memoize(fn): fn.cache = {} @wraps(fn) def func(*args, **kwargs): key = str(args) + str(kwargs) if key not in fn.cache: fn.cache[key] = fn(*args, **kwargs) return fn.cache[key] return func blockdiag-1.5.3/src/blockdiag/imagedraw/utils/ellipse.py0000644000076600000240000000350112217202617024054 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import math from blockdiag.utils import XY DIVISION = 1000.0 CYCLE = 10 def _angles(du, a, b, start, end): phi = (start / 180.0) * math.pi while phi <= (end / 180.0) * math.pi: yield phi phi += du / math.sqrt((a * math.sin(phi)) ** 2 + (b * math.cos(phi)) ** 2) def _coordinates(du, a, b, start, end): for angle in _angles(du, a, b, start, end): yield (a * math.cos(angle), b * math.sin(angle)) def endpoints(du, a, b, start, end): pt1 = next(iter(_coordinates(du, a, b, start, start + 1))) pt2 = next(iter(_coordinates(du, a, b, end, end + 1))) return [XY(*pt1), XY(*pt2)] def dots(box, cycle, start=0, end=360): # calcrate rendering pattern from cycle base = 0 rendered = [] for index in range(0, len(cycle), 2): i, j = cycle[index:index + 2] for n in range(base * 2, (base + i) * 2): rendered.append(n) base += i + j a = float(box.width) / 2 b = float(box.height) / 2 du = 1 _max = sum(cycle) * 2 center = box.center for i, coord in enumerate(_coordinates(du, a, b, start, end)): if i % _max in rendered: yield XY(center.x + coord[0], center.y + coord[1]) blockdiag-1.5.3/src/blockdiag/imagedraw/utils/pillow.py0000644000076600000240000000234112366150740023732 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. def patch_FreeTypeFont_getsize(): try: from PIL import PILLOW_VERSION from PIL.ImageFont import FreeTypeFont # Avoid offset problem in Pillow (>= 2.2.0, < 2.6.0) if "2.2.0" <= PILLOW_VERSION < "2.6.0": original_getsize = FreeTypeFont.getsize def getsize(self, string): size = original_getsize(self, string) offset = self.getoffset(string) return (size[0] + offset[0], size[1] + offset[1]) FreeTypeFont.getsize = getsize except ImportError: pass def apply_patch(): patch_FreeTypeFont_getsize() blockdiag-1.5.3/src/blockdiag/metrics.py0000644000076600000240000011075712354157065021011 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import copy from collections import defaultdict from blockdiag import noderenderer from blockdiag.elements import DiagramNode from blockdiag.utils import Box, Size, XY from blockdiag.utils.fontmap import FontInfo, FontMap cellsize = 8 class EdgeLines(object): def __init__(self): self.xy = None self.stroking = False self.polylines = [] def moveTo(self, x, y=None): self.stroking = False if y is None: self.xy = x else: self.xy = XY(x, y) def lineTo(self, x, y=None): if y is None: elem = x else: elem = XY(x, y) if self.stroking is False: self.stroking = True polyline = [] if self.xy: polyline.append(self.xy) self.polylines.append(polyline) if len(self.polylines[-1]) > 0: if self.polylines[-1][-1] == elem: return self.polylines[-1].append(elem) def lines(self): lines = [] for line in self.polylines: start = line[0] for elem in list(line[1:]): lines.append((start, elem)) start = elem return lines class AutoScaler(object): def __init__(self, subject, scale_ratio): self.subject = subject self.scale_ratio = scale_ratio def __getattr__(self, name): ratio = self.scale_ratio return self.scale(getattr(self.subject, name), ratio) def __getitem__(self, name): ratio = self.scale_ratio return self.scale(self.subject[name], ratio) @classmethod def scale(cls, value, ratio): if not callable(value): return cls._scale(value, ratio) else: def _(*args, **kwargs): ret = value(*args, **kwargs) return cls._scale(ret, ratio) return _ @classmethod def _scale(cls, value, ratio): if ratio == 1: return value klass = value.__class__ if klass == XY: ret = XY(value.x * ratio, value.y * ratio) elif klass == Size: ret = Size(value.width * ratio, value.height * ratio) elif klass == Box: ret = Box(value[0] * ratio, value[1] * ratio, value[2] * ratio, value[3] * ratio) elif klass == tuple: ret = tuple([cls.scale(x, ratio) for x in value]) elif klass == list: ret = [cls.scale(x, ratio) for x in value] elif klass == EdgeLines: ret = EdgeLines() ret.polylines = cls.scale(value.polylines, ratio) elif klass == FontInfo: ret = FontInfo(value.familyname, value.path, value.size * ratio) elif klass == int: ret = value * ratio elif klass == str: ret = value else: ret = cls(value, ratio) return ret @property def original_metrics(self): return self.subject class DiagramMetrics(object): cellsize = cellsize edge_layout = 'normal' node_padding = 4 line_spacing = 2 shadow_offset = XY(3, 6) page_margin = XY(0, 0) page_padding = [0, 0, 0, 0] node_width = cellsize * 16 node_height = cellsize * 5 span_width = cellsize * 8 span_height = cellsize * 5 def __init__(self, diagram, drawer=None, fontmap=None): self.drawer = drawer if diagram.node_width is not None: self.node_width = diagram.node_width if diagram.node_height is not None: self.node_height = diagram.node_height if diagram.span_width is not None: self.span_width = diagram.span_width if diagram.span_height is not None: self.span_height = diagram.span_height if fontmap is not None: self.fontmap = fontmap else: self.fontmap = FontMap() if diagram.page_padding is not None: self.page_padding = diagram.page_padding if diagram.edge_layout is not None: self.edge_layout = diagram.edge_layout # setup spreadsheet sheet = self.spreadsheet = SpreadSheetMetrics(self) nodes = [n for n in diagram.traverse_nodes() if n.drawable] node_width = self.node_width for x in range(diagram.colwidth): widths = [n.width for n in nodes if n.xy.x == x] if widths: width = max(n or node_width for n in widths) sheet.set_node_width(x, width) node_height = self.node_height for y in range(diagram.colheight): heights = [n.height for n in nodes if n.xy.y == y] if heights: height = max(n or node_height for n in heights) sheet.set_node_height(y, height) @property def original_metrics(self): return self def shift(self, x, y): metrics = copy.copy(self) metrics.spreadsheet = copy.copy(self.spreadsheet) metrics.spreadsheet.metrics = metrics metrics.page_margin = XY(x, y) return metrics def textsize(self, string, font=None, width=65535): return self.drawer.textsize(string, font, maxwidth=width) def node(self, node): renderer = noderenderer.get(node.shape) if hasattr(renderer, 'render'): return renderer(node, self) else: return self.cell(node) def cell(self, node, use_padding=True): return self.spreadsheet.node(node, use_padding) def group(self, group): return self.spreadsheet.node(group, use_padding=False) def edge(self, edge): if self.edge_layout == 'flowchart': if edge.node1.group.orientation == 'landscape': return FlowchartLandscapeEdgeMetrics(edge, self) else: return FlowchartPortraitEdgeMetrics(edge, self) else: if edge.node1.group.orientation == 'landscape': return LandscapeEdgeMetrics(edge, self) else: return PortraitEdgeMetrics(edge, self) def font_for(self, element): return self.fontmap.find(element) def pagesize(self, width, height): return self.spreadsheet.pagesize(width, height) class SubMetrics(object): def __getattr__(self, name): # avoid recursion-error on Python 2.6 if 'metrics' not in self.__dict__: raise AttributeError() return getattr(self.metrics, name) class SpreadSheetMetrics(SubMetrics): def __init__(self, metrics): self.metrics = metrics self.node_width = defaultdict(lambda: metrics.node_width) self.node_height = defaultdict(lambda: metrics.node_height) self.span_width = defaultdict(lambda: metrics.span_width) self.span_height = defaultdict(lambda: metrics.span_height) def set_node_width(self, x, width): if (width is not None and 0 < width and (x not in self.node_width or self.node_width[x] < width)): self.node_width[x] = width def set_node_height(self, y, height): if (height is not None and 0 < height and (y not in self.node_height or self.node_height[y] < height)): self.node_height[y] = height def set_span_width(self, x, width): if (width is not None and 0 < width and (x not in self.span_width or self.span_width[x] < width)): self.span_width[x] = width def add_span_width(self, x, width): self.span_width[x] += width def set_span_height(self, y, height): if (height is not None and 0 < height and (y not in self.span_height or self.span_height[y] < height)): self.span_height[y] = height def add_span_height(self, y, height): self.span_height[y] += height def node(self, node, use_padding=True): x1, y1 = self._node_topleft(node, use_padding) x2, y2 = self._node_bottomright(node, use_padding) return NodeMetrics(self.metrics, x1, y1, x2, y2) def _node_topleft(self, node, use_padding=True): x, y = node.xy margin = self.page_margin padding = self.page_padding node_width = sum(self.node_width[i] for i in range(x)) node_height = sum(self.node_height[i] for i in range(y)) span_width = sum(self.span_width[i] for i in range(x + 1)) span_height = sum(self.span_height[i] for i in range(y + 1)) if use_padding: width = node.width or self.metrics.node_width xdiff = (self.node_width[x] - width) // 2 if xdiff < 0: xdiff = 0 height = node.height or self.metrics.node_height ydiff = (self.node_height[y] - height) // 2 if ydiff < 0: ydiff = 0 else: xdiff = 0 ydiff = 0 x1 = margin.x + padding[3] + node_width + span_width + xdiff y1 = margin.y + padding[0] + node_height + span_height + ydiff return XY(x1, y1) def _node_bottomright(self, node, use_padding=True): x = node.xy.x + node.colwidth - 1 y = node.xy.y + node.colheight - 1 margin = self.page_margin padding = self.page_padding node_width = sum(self.node_width[i] for i in range(x + 1)) node_height = sum(self.node_height[i] for i in range(y + 1)) span_width = sum(self.span_width[i] for i in range(x + 1)) span_height = sum(self.span_height[i] for i in range(y + 1)) if use_padding: width = node.width or self.metrics.node_width xdiff = (self.node_width[x] - width) // 2 if xdiff < 0: xdiff = 0 height = node.height or self.metrics.node_height ydiff = (self.node_height[y] - height) // 2 if ydiff < 0: ydiff = 0 else: xdiff = 0 ydiff = 0 x2 = margin.x + padding[3] + node_width + span_width - xdiff y2 = margin.y + padding[0] + node_height + span_height - ydiff return XY(x2, y2) def pagesize(self, width, height): margin = self.metrics.page_margin padding = self.metrics.page_padding dummy = DiagramNode(None) dummy.xy = XY(width - 1, height - 1) x, y = self._node_bottomright(dummy, use_padding=False) x_span = self.span_width[width] y_span = self.span_height[height] return Size(x + margin.x + padding[1] + x_span, y + margin.y + padding[2] + y_span) class NodeMetrics(SubMetrics): def __init__(self, metrics, x1, y1, x2, y2): self.metrics = metrics self._box = Box(x1, y1, x2, y2) def __getattr__(self, name): if hasattr(self._box, name): return getattr(self._box, name) else: return getattr(self.metrics, name) def __getitem__(self, key): return self.box[key] @property def box(self): return self._box @property def marginbox(self): return Box(self._box.x1 - self.span_width // 8, self._box.y1 - self.span_height // 4, self._box.x2 + self.span_width // 8, self._box.y2 + self.span_height // 4) @property def corebox(self): return Box(self._box.x1 + self.node_padding, self._box.y1 + self.node_padding, self._box.x2 - self.node_padding * 2, self._box.y2 - self.node_padding * 2) @property def grouplabelbox(self): return Box(self._box.x1, self._box.y1 - self.span_height // 2, self._box.x2, self._box.y1) class EdgeMetrics(SubMetrics): def __init__(self, edge, metrics): self.metrics = metrics self.edge = edge @property def headshapes(self): pass @property def _shaft(self): pass @property def heads(self): heads = [] head1, head2 = self.headshapes if head1: heads.append(self._head(self.edge.node1, head1)) if head2: heads.append(self._head(self.edge.node2, head2)) return heads def _head(self, node, direct): head = [] cell = self.cellsize node = self.node(node) if direct == 'up': xy = node.bottom head.append(XY(xy.x, xy.y + 1)) head.append(XY(xy.x - cell // 2, xy.y + cell)) head.append(XY(xy.x, xy.y + cell * 2)) head.append(XY(xy.x + cell // 2, xy.y + cell)) head.append(XY(xy.x, xy.y + 1)) elif direct == 'down': xy = node.top head.append(XY(xy.x, xy.y - 1)) head.append(XY(xy.x - cell // 2, xy.y - cell)) head.append(XY(xy.x, xy.y - cell * 2)) head.append(XY(xy.x + cell // 2, xy.y - cell)) head.append(XY(xy.x, xy.y - 1)) elif direct == 'right': xy = node.left head.append(XY(xy.x - 1, xy.y)) head.append(XY(xy.x - cell, xy.y - cell // 2)) head.append(XY(xy.x - cell * 2, xy.y)) head.append(XY(xy.x - cell, xy.y + cell // 2)) head.append(XY(xy.x - 1, xy.y)) elif direct == 'left': xy = node.right head.append(XY(xy.x + 1, xy.y)) head.append(XY(xy.x + cell, xy.y - cell // 2)) head.append(XY(xy.x + cell * 2, xy.y)) head.append(XY(xy.x + cell, xy.y + cell // 2)) head.append(XY(xy.x + 1, xy.y)) elif direct == 'rup': xy = node.bottom head.append(XY(xy.x, xy.y + cell)) head.append(XY(xy.x - cell, xy.y + 1)) head.append(XY(xy.x, xy.y + 1 * 2)) head.append(XY(xy.x + cell, xy.y + 1)) head.append(XY(xy.x, xy.y + cell)) elif direct == 'rdown': xy = node.top head.append(XY(xy.x, xy.y - cell)) head.append(XY(xy.x - cell, xy.y - 1)) head.append(XY(xy.x, xy.y - 1 * 2)) head.append(XY(xy.x + cell, xy.y - 1)) head.append(XY(xy.x, xy.y - cell)) elif direct == 'rright': xy = node.left head.append(XY(xy.x - cell, xy.y)) head.append(XY(xy.x - 1, xy.y - cell)) head.append(XY(xy.x - 1 * 2, xy.y)) head.append(XY(xy.x - 1, xy.y + cell)) head.append(XY(xy.x - cell, xy.y)) elif direct == 'rleft': xy = node.right head.append(XY(xy.x + cell, xy.y)) head.append(XY(xy.x + 1, xy.y - cell)) head.append(XY(xy.x + 1 * 2, xy.y)) head.append(XY(xy.x + 1, xy.y + cell)) head.append(XY(xy.x + cell, xy.y)) if self.edge.hstyle not in ('composition', 'aggregation'): head.pop(2) return head @property def shaft(self): cell = self.cellsize lines = self._shaft head1, head2 = self.headshapes if head1: pt = lines.polylines[0].pop(0) if head1 == 'up': lines.polylines[0].insert(0, XY(pt.x, pt.y + cell)) elif head1 == 'right': lines.polylines[0].insert(0, XY(pt.x - cell, pt.y)) elif head1 == 'left': lines.polylines[0].insert(0, XY(pt.x + cell, pt.y)) elif head1 == 'down': lines.polylines[0].insert(0, XY(pt.x, pt.y - cell)) elif head1 == 'rup': lines.polylines[0].insert(0, XY(pt.x, pt.y + cell)) elif head1 == 'rright': lines.polylines[0].insert(0, XY(pt.x - cell, pt.y)) elif head1 == 'rleft': lines.polylines[0].insert(0, XY(pt.x + cell, pt.y)) elif head1 == 'rdown': lines.polylines[0].insert(0, XY(pt.x, pt.y - cell)) if head2: pt = lines.polylines[-1].pop() if head2 == 'up': lines.polylines[-1].append(XY(pt.x, pt.y + cell)) elif head2 == 'right': lines.polylines[-1].append(XY(pt.x - cell, pt.y)) elif head2 == 'left': lines.polylines[-1].append(XY(pt.x + cell, pt.y)) elif head2 == 'down': lines.polylines[-1].append(XY(pt.x, pt.y - cell)) elif head2 == 'rup': lines.polylines[-1].append(XY(pt.x, pt.y + cell)) elif head2 == 'rright': lines.polylines[-1].append(XY(pt.x - cell, pt.y)) elif head2 == 'rleft': lines.polylines[-1].append(XY(pt.x + cell, pt.y)) elif head2 == 'rdown': lines.polylines[-1].append(XY(pt.x, pt.y - cell)) return lines @property def labelbox(self): pass class LandscapeEdgeMetrics(EdgeMetrics): @property def headshapes(self): heads = [] _dir = self.edge.direction if self.edge.dir in ('back', 'both'): if _dir in ('left-up', 'left', 'same', 'right-up', 'right', 'right-down'): heads.append('left') elif _dir == 'up': if self.edge.skipped: heads.append('left') else: heads.append('down') elif _dir in ('left-down', 'down'): if self.edge.skipped: heads.append('left') else: heads.append('up') if self.edge.hstyle in ('manyone', 'manymany'): heads[-1] = 'r' + heads[-1] else: heads.append(None) if self.edge.dir in ('forward', 'both'): if _dir in ('right-up', 'right', 'right-down'): heads.append('right') elif _dir == 'up': heads.append('up') elif _dir in ('left-up', 'left', 'left-down', 'down', 'same'): heads.append('down') if self.edge.hstyle in ('onemany', 'manymany'): heads[-1] = 'r' + heads[-1] else: heads.append(None) return heads @property def _shaft(self): span = XY(self.span_width, self.span_height) _dir = self.edge.direction node1 = self.node(self.edge.node1) cell1 = self.cell(self.edge.node1, use_padding=False) node2 = self.node(self.edge.node2) cell2 = self.cell(self.edge.node2, use_padding=False) shaft = EdgeLines() if _dir == 'right': shaft.moveTo(node1.right) if self.edge.skipped: shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 2, cell1.bottomright.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.bottomright.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) shaft.lineTo(node2.left) elif _dir == 'right-up': shaft.moveTo(node1.right) if self.edge.skipped: shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 2, cell2.bottomleft.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.bottomleft.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) else: shaft.lineTo(cell2.left.x - span.x // 4, cell1.right.y) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) shaft.lineTo(node2.left) elif _dir == 'right-down': shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) if self.edge.skipped: shaft.lineTo(cell1.right.x + span.x // 2, cell2.topleft.y - span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.topleft.y - span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) else: shaft.lineTo(cell1.right.x + span.x // 2, cell2.left.y) shaft.lineTo(node2.left) elif _dir == 'up': if self.edge.skipped: shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 4, cell2.bottom.y + span.y // 2) shaft.lineTo(cell2.bottom.x, cell2.bottom.y + span.y // 2) else: shaft.moveTo(node1.top) shaft.lineTo(node2.bottom) elif _dir in ('left-up', 'left', 'same'): shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 4, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(node2.top) elif _dir == 'left-down': if self.edge.skipped: shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 2, cell2.top.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) else: shaft.moveTo(node1.bottom) shaft.lineTo(cell1.bottom.x, cell2.top.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) shaft.lineTo(node2.top) elif _dir == 'down': if self.edge.skipped: shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 2, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 2, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2 + span.y // 8) else: shaft.moveTo(node1.bottom) shaft.lineTo(node2.top) return shaft @property def labelbox(self): span = XY(self.span_width, self.span_height) node = XY(self.node_width, self.node_height) _dir = self.edge.direction node1 = self.cell(self.edge.node1, use_padding=False) node2 = self.cell(self.edge.node2, use_padding=False) if _dir == 'right': if self.edge.skipped: box = Box(node1.bottomright.x + span.x, node1.bottomright.y, node2.bottomleft.x - span.x, node2.bottomleft.y + span.y // 2) else: box = Box(node1.topright.x, node1.topright.y - span.y // 8, node2.left.x, node2.left.y - span.y // 8) elif _dir == 'right-up': box = Box(node2.left.x - span.x, node1.top.y - node.y // 2, node2.bottomleft.x, node1.top.y) elif _dir == 'right-down': box = Box(node1.right.x, node2.topleft.y - span.y // 8, node1.right.x + span.x, node2.left.y - span.y // 8) elif _dir in ('up', 'left-up', 'left', 'same'): if self.edge.node2.xy.y < self.edge.node1.xy.y: box = Box(node1.topright.x - span.x // 2 + span.x // 4, node1.topright.y - span.y // 2, node1.topright.x + span.x // 2 + span.x // 4, node1.topright.y) else: box = Box(node1.top.x + span.x // 4, node1.top.y - span.y, node1.topright.x + span.x // 4, node1.topright.y - span.y // 2) elif _dir in ('left-down', 'down'): box = Box(node2.top.x + span.x // 4, node2.top.y - span.y, node2.topright.x + span.x // 4, node2.topright.y - span.y // 2) # shrink box box = Box(box[0] + span.x // 8, box[1], box[2] - span.x // 8, box[3]) return box class PortraitEdgeMetrics(EdgeMetrics): @property def headshapes(self): heads = [] _dir = self.edge.direction if self.edge.dir in ('back', 'both'): if _dir == 'right': if self.edge.skipped: heads.append('up') else: heads.append('left') elif _dir in ('up', 'right-up', 'same'): heads.append('up') elif _dir in ('left-up', 'left'): heads.append('left') elif _dir in ('left-down', 'down', 'right-down'): if self.edge.skipped: heads.append('left') else: heads.append('up') if self.edge.hstyle in ('manyone', 'manymany'): heads[-1] = 'r' + heads[-1] else: heads.append(None) if self.edge.dir in ('forward', 'both'): if _dir == 'right': if self.edge.skipped: heads.append('down') else: heads.append('right') elif _dir in ('up', 'right-up', 'same'): heads.append('down') elif _dir in ('left-up', 'left', 'left-down', 'down', 'right-down'): heads.append('down') if self.edge.hstyle in ('onemany', 'manymany'): heads[-1] = 'r' + heads[-1] else: heads.append(None) return heads @property def _shaft(self): span = XY(self.span_width, self.span_height) _dir = self.edge.direction node1 = self.node(self.edge.node1) cell1 = self.cell(self.edge.node1, use_padding=False) node2 = self.node(self.edge.node2) cell2 = self.cell(self.edge.node2, use_padding=False) shaft = EdgeLines() if _dir in ('up', 'right-up', 'same', 'right'): if _dir == 'right' and not self.edge.skipped: shaft.moveTo(node1.right) shaft.lineTo(node2.left) else: shaft.moveTo(node1.bottom) shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 4, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 4, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(node2.top) elif _dir == 'right-down': shaft.moveTo(node1.bottom) shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) if self.edge.skipped: shaft.lineTo(cell2.left.x - span.x // 2, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.topleft.x - span.x // 2, cell2.topleft.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) else: shaft.lineTo(cell2.top.x, cell1.bottom.y + span.y // 2) shaft.lineTo(node2.top) elif _dir in ('left-up', 'left', 'same'): shaft.moveTo(node1.right) shaft.lineTo(cell1.right.x + span.x // 4, cell1.right.y) shaft.lineTo(cell1.right.x + span.x // 4, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2 + span.y // 8) shaft.lineTo(node2.top) elif _dir == 'left-down': shaft.moveTo(node1.bottom) if self.edge.skipped: shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 2, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 2, cell2.top.y - span.y // 2) else: shaft.lineTo(cell1.bottom.x, cell2.top.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) shaft.lineTo(node2.top) elif _dir == 'down': shaft.moveTo(node1.bottom) if self.edge.skipped: shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) shaft.lineTo(cell1.right.x + span.x // 2, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.right.x + span.x // 2, cell2.top.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) shaft.lineTo(node2.top) return shaft @property def labelbox(self): span = XY(self.span_width, self.span_height) _dir = self.edge.direction node1 = self.cell(self.edge.node1, use_padding=False) node2 = self.cell(self.edge.node2, use_padding=False) if _dir == 'right': if self.edge.skipped: box = Box(node1.bottomright.x + span.x, node1.bottomright.y, node2.bottomleft.x - span.x, node2.bottomleft.y + span.y // 2) else: box = Box(node1.topright.x, node1.topright.y - span.y // 8, node2.left.x, node2.left.y - span.y // 8) elif _dir == 'right-up': box = Box(node2.left.x - span.x, node2.left.y, node2.bottomleft.x, node2.bottomleft.y) elif _dir == 'right-down': box = Box(node2.topleft.x, node2.topleft.y - span.y // 2, node2.top.x, node2.top.y) elif _dir in ('up', 'left-up', 'left', 'same'): if self.edge.node2.xy.y < self.edge.node1.xy.y: box = Box(node1.topright.x - span.x // 2 + span.x // 4, node1.topright.y - span.y // 2, node1.topright.x + span.x // 2 + span.x // 4, node1.topright.y) else: box = Box(node1.top.x + span.x // 4, node1.top.y - span.y, node1.topright.x + span.x // 4, node1.topright.y - span.y // 2) elif _dir == 'down': box = Box(node2.top.x + span.x // 4, node2.top.y - span.y // 2, node2.topright.x + span.x // 4, node2.topright.y) elif _dir == 'left-down': box = Box(node1.bottomleft.x, node1.bottomleft.y, node1.bottom.x, node1.bottom.y + span.y // 2) # shrink box box = Box(box[0] + span.x // 8, box[1], box[2] - span.x // 8, box[3]) return box class FlowchartLandscapeEdgeMetrics(LandscapeEdgeMetrics): @property def headshapes(self): heads = [] if self.edge.direction == 'right-down': if self.edge.dir in ('back', 'both'): if self.edge.hstyle in ('manyone', 'manymany'): heads.append('rup') else: heads.append('up') else: heads.append(None) if self.edge.dir in ('forward', 'both'): if self.edge.hstyle in ('onemany', 'manymany'): heads.append('rright') else: heads.append('right') else: heads.append(None) else: heads = super(FlowchartLandscapeEdgeMetrics, self).headshapes return heads @property def _shaft(self): if self.edge.direction == 'right-down': span = XY(self.span_width, self.span_height) node1 = self.node(self.edge.node1) cell1 = self.cell(self.edge.node1, use_padding=False) node2 = self.node(self.edge.node2) cell2 = self.cell(self.edge.node2, use_padding=False) shaft = EdgeLines() shaft.moveTo(node1.bottom) if self.edge.skipped: shaft.lineTo(cell1.bottom.x, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell1.bottom.y + span.y // 2) shaft.lineTo(cell2.left.x - span.x // 4, cell2.left.y) else: shaft.lineTo(cell1.bottom.x, cell2.left.y) shaft.lineTo(node2.left) else: shaft = super(FlowchartLandscapeEdgeMetrics, self)._shaft return shaft @property def labelbox(self): _dir = self.edge.direction if _dir == 'right': span = XY(self.span_width, self.span_height) cell1 = self.cell(self.edge.node1, use_padding=False) cell2 = self.cell(self.edge.node2, use_padding=False) if self.edge.skipped: box = Box(cell1.bottom.x, cell1.bottom.y, cell1.bottomright.x, cell1.bottomright.y + span.y // 2) else: box = Box(cell1.bottom.x, cell2.left.y - span.y // 2, cell1.bottom.x, cell2.left.y) else: box = super(FlowchartLandscapeEdgeMetrics, self).labelbox return box class FlowchartPortraitEdgeMetrics(PortraitEdgeMetrics): @property def headshapes(self): heads = [] if self.edge.direction == 'right-down': if self.edge.dir in ('back', 'both'): if self.edge.hstyle in ('manyone', 'manymany'): heads.append('left') else: heads.append('left') else: heads.append(None) if self.edge.dir in ('forward', 'both'): if self.edge.dir in ('onemany', 'manymany'): heads.append('rdown') else: heads.append('down') else: heads.append(None) else: heads = super(FlowchartPortraitEdgeMetrics, self).headshapes return heads @property def _shaft(self): if self.edge.direction == 'right-down': span = XY(self.span_width, self.span_height) node1 = self.node(self.edge.node1) cell1 = self.cell(self.edge.node1, use_padding=False) node2 = self.node(self.edge.node2) cell2 = self.cell(self.edge.node2, use_padding=False) shaft = EdgeLines() shaft.moveTo(node1.right) if self.edge.skipped: shaft.lineTo(cell1.right.x + span.x * 3 // 4, cell1.right.y) shaft.lineTo(cell1.right.x + span.x * 3 // 4, cell2.topleft.y - span.y // 2) shaft.lineTo(cell2.top.x, cell2.top.y - span.y // 2) else: shaft.lineTo(cell2.top.x, cell1.right.y) shaft.lineTo(node2.top) else: shaft = super(FlowchartPortraitEdgeMetrics, self)._shaft return shaft @property def labelbox(self): _dir = self.edge.direction span = XY(self.span_width, self.span_height) cell1 = self.cell(self.edge.node1, use_padding=False) cell2 = self.cell(self.edge.node2, use_padding=False) if _dir == 'down': box = Box(cell2.topleft.x, cell2.top.y - span.y // 2, cell2.top.x, cell2.top.y) elif _dir == 'right': if self.edge.skipped: box = Box(cell1.bottom.x, cell1.bottom.y, cell1.bottomright.x, cell1.bottomright.y + span.y // 2) else: box = Box(cell1.bottom.x, cell2.left.y - span.y // 2, cell1.bottom.x, cell2.left.y) else: box = super(FlowchartPortraitEdgeMetrics, self).labelbox return box blockdiag-1.5.3/src/blockdiag/noderenderer/0000755000076600000240000000000012556430040021421 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/noderenderer/__init__.py0000644000076600000240000000257712351302200023532 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import pkg_resources from blockdiag.noderenderer.base import NodeShape # NOQA: backward compatibility renderers = {} searchpath = [] def init_renderers(): for plugin in pkg_resources.iter_entry_points('blockdiag_noderenderer'): module = plugin.load() if hasattr(module, 'setup'): module.setup(module) def install_renderer(name, renderer): renderers[name] = renderer def set_default_namespace(path): searchpath[:] = [] for path in path.split(','): searchpath.append(path) def get(shape): if not renderers: init_renderers() for path in searchpath: name = "%s.%s" % (path, shape) if name in renderers: return renderers[name] return renderers.get(shape) blockdiag-1.5.3/src/blockdiag/noderenderer/actor.py0000644000076600000240000001116112351443633023110 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import XY, Box, Size class Actor(NodeShape): def __init__(self, node, metrics=None): super(Actor, self).__init__(node, metrics) m = metrics.cell(node) if node.label: font = metrics.font_for(self.node) textsize = metrics.textsize(node.label, font) shortside = min(m.width, m.height - textsize.height) else: textsize = Size(0, 0) shortside = min(m.width, m.height) r = self.radius = shortside // 8 # radius of actor's head self.center = metrics.cell(node).center self.connectors[0] = XY(self.center.x, self.center.y - r * 9 // 2) self.connectors[1] = XY(self.center.x + r * 4, self.center.y) self.connectors[2] = XY(self.center.x, self.center.y + r * 4 + textsize.height) self.connectors[3] = XY(self.center.x - r * 4, self.center.y) self.textbox = Box(m.left.x, self.center.y + r * 4, m.right.x, self.connectors[2].y) def head_part(self): r = self.radius * 3 // 2 pt = self.metrics.cell(self.node).center.shift(y=-self.radius * 3) return Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r) def body_part(self): r = self.radius m = self.metrics.cell(self.node) bodyC = m.center neckWidth = r * 2 // 3 # neck size arm = r * 4 # arm length armWidth = r bodyWidth = r * 2 // 3 # half of body width bodyHeight = r legXout = r * 7 // 2 # toe outer position legYout = bodyHeight + r * 3 legXin = r * 2 # toe inner position legYin = bodyHeight + r * 3 return [XY(bodyC.x + neckWidth, bodyC.y - r * 2), XY(bodyC.x + neckWidth, bodyC.y - armWidth), # neck end XY(bodyC.x + arm, bodyC.y - armWidth), XY(bodyC.x + arm, bodyC.y), # right arm end XY(bodyC.x + bodyWidth, bodyC.y), # right body end XY(bodyC.x + bodyWidth, bodyC.y + bodyHeight), XY(bodyC.x + legXout, bodyC.y + legYout), XY(bodyC.x + legXin, bodyC.y + legYin), XY(bodyC.x, bodyC.y + (bodyHeight * 2)), # body bottom center XY(bodyC.x - legXin, bodyC.y + legYin), XY(bodyC.x - legXout, bodyC.y + legYout), XY(bodyC.x - bodyWidth, bodyC.y + bodyHeight), XY(bodyC.x - bodyWidth, bodyC.y), # left body end XY(bodyC.x - arm, bodyC.y), XY(bodyC.x - arm, bodyC.y - armWidth), XY(bodyC.x - neckWidth, bodyC.y - armWidth), # left arm end XY(bodyC.x - neckWidth, bodyC.y - r * 2)] def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw body part body = self.body_part() if kwargs.get('shadow'): body = self.shift_shadow(body) if kwargs.get('style') == 'blur': drawer.polygon(body, fill=fill, filter='transp-blur') else: drawer.polygon(body, fill=fill) else: drawer.polygon(body, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # draw head part head = self.head_part() if kwargs.get('shadow'): head = self.shift_shadow(head) if kwargs.get('style') == 'blur': drawer.ellipse(head, fill=fill, outline=self.node.linecolor, filter='transp-blur') else: drawer.ellipse(head, fill=fill, outline=self.node.linecolor) else: drawer.ellipse(head, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('actor', Actor) blockdiag-1.5.3/src/blockdiag/noderenderer/base.py0000644000076600000240000001163512351327262022717 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.utils import images, Box, XY class NodeShape(object): def __init__(self, node, metrics): self.node = node self.metrics = metrics m = self.metrics.cell(self.node) self.textalign = 'center' self.connectors = [m.top, m.right, m.bottom, m.left] if node.icon is None: self.iconbox = None self.textbox = m.box else: image_size = images.get_image_size(node.icon) if image_size is None: iconsize = (0, 0) else: boundedbox = [metrics.node_width // 2, metrics.node_height] iconsize = images.calc_image_size(image_size, boundedbox) vmargin = (metrics.node_height - iconsize[1]) // 2 self.iconbox = Box(m.topleft.x, m.topleft.y + vmargin, m.topleft.x + iconsize[0], m.topleft.y + vmargin + iconsize[1]) self.textbox = Box(self.iconbox[2], m.top.y, m.bottomright.x, m.bottomright.y) def render(self, drawer, _format, **kwargs): if self.node.stacked and not kwargs.get('stacked'): node = self.node.duplicate() node.label = "" node.background = "" for i in range(2, 0, -1): # use original_metrics FORCE r = self.metrics.original_metrics.cellsize // 2 * i metrics = self.metrics.shift(r, r) self.__class__(node, metrics).render(drawer, _format, stacked=True, **kwargs) if hasattr(self, 'render_vector_shape') and _format == 'SVG': self.render_vector_shape(drawer, _format, **kwargs) else: self.render_shape(drawer, _format, **kwargs) self.render_icon(drawer, **kwargs) self.render_label(drawer, **kwargs) self.render_number_badge(drawer, **kwargs) def render_icon(self, drawer, **kwargs): if self.node.icon is not None and kwargs.get('shadow') is not True: drawer.image(self.iconbox, self.node.icon) def render_shape(self, drawer, _, **kwargs): pass def render_label(self, drawer, **kwargs): if not kwargs.get('shadow'): font = self.metrics.font_for(self.node) drawer.textarea(self.textbox, self.node.label, font, rotate=self.node.rotate, fill=self.node.textcolor, halign=self.textalign, line_spacing=self.metrics.line_spacing, orientation=self.node.label_orientation) def render_number_badge(self, drawer, **kwargs): if self.node.numbered is not None and kwargs.get('shadow') is None: badgeFill = kwargs.get('badgeFill') xy = self.metrics.cell(self.node).topleft r = self.metrics.cellsize * 3 // 2 box = Box(xy.x - r, xy.y - r, xy.x + r, xy.y + r) font = self.metrics.font_for(self.node) drawer.ellipse(box, outline=self.node.linecolor, fill=badgeFill) drawer.textarea(box, self.node.numbered, font, rotate=self.node.rotate, fill=self.node.textcolor) @property def top(self): return self.connectors[0] @property def left(self): return self.connectors[3] @property def right(self): point = self.connectors[1] if self.node.stacked: point = XY(point.x + self.metrics.cellsize, point.y) return point @property def bottom(self): point = self.connectors[2] if self.node.stacked: point = XY(point.x, point.y + self.metrics.cellsize) return point def shift_shadow(self, value): xdiff = self.metrics.shadow_offset.x ydiff = self.metrics.shadow_offset.y if isinstance(value, XY): ret = XY(value.x + xdiff, value.y + ydiff) elif isinstance(value, Box): ret = Box(value.x1 + xdiff, value.y1 + ydiff, value.x2 + xdiff, value.y2 + ydiff) elif isinstance(value, (list, tuple)): ret = [self.shift_shadow(x) for x in value] return ret blockdiag-1.5.3/src/blockdiag/noderenderer/beginpoint.py0000644000076600000240000000437612351302303024134 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import XY, Box class BeginPoint(NodeShape): def __init__(self, node, metrics=None): super(BeginPoint, self).__init__(node, metrics) m = metrics.cell(node) self.radius = metrics.cellsize self.center = m.center self.textbox = Box(m.top.x, m.top.y, m.right.x, m.right.y) self.textalign = 'left' self.connectors = [XY(self.center.x, self.center.y - self.radius), XY(self.center.x + self.radius, self.center.y), XY(self.center.x, self.center.y + self.radius), XY(self.center.x - self.radius, self.center.y)] def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline r = self.radius box = Box(self.center.x - r, self.center.y - r, self.center.x + r, self.center.y + r) if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(box, fill=fill, outline=fill) else: if self.node.color == self.node.basecolor: color = self.node.linecolor else: color = self.node.color drawer.ellipse(box, fill=color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('beginpoint', BeginPoint) blockdiag-1.5.3/src/blockdiag/noderenderer/box.py0000644000076600000240000000334512351302307022565 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape class Box(NodeShape): def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline box = self.metrics.cell(self.node).box if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.rectangle(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(box, fill=fill, outline=fill) elif self.node.background: drawer.rectangle(box, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.rectangle(box, outline=self.node.linecolor, style=self.node.style) else: drawer.rectangle(box, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('box', Box) blockdiag-1.5.3/src/blockdiag/noderenderer/circle.py0000644000076600000240000000445512351302315023240 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box, XY class Circle(NodeShape): def __init__(self, node, metrics=None): super(Circle, self).__init__(node, metrics) cell = metrics.cell(node) r = min(cell.box.width, cell.box.height) // 2 + \ metrics.cellsize // 2 pt = metrics.cell(node).center self.connectors = [XY(pt.x, pt.y - r), # top XY(pt.x + r, pt.y), # right XY(pt.x, pt.y + r), # bottom XY(pt.x - r, pt.y)] # left self.textbox = Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline if kwargs.get('shadow'): box = self.shift_shadow(self.textbox) if kwargs.get('style') == 'blur': drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(box, fill=fill, outline=fill) elif self.node.background: drawer.ellipse(self.textbox, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.ellipse(self.textbox, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.ellipse(self.textbox, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('circle', Circle) blockdiag-1.5.3/src/blockdiag/noderenderer/cloud.py0000644000076600000240000001312412351302320023072 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box from blockdiag.imagedraw.simplesvg import pathdata class Cloud(NodeShape): def __init__(self, node, metrics=None): super(Cloud, self).__init__(node, metrics) pt = metrics.cell(node).topleft rx = (self.node.width or self.metrics.node_width) // 12 ry = (self.node.height or self.metrics.node_height) // 5 self.textbox = Box(pt.x + rx * 2, pt.y + ry, pt.x + rx * 11, pt.y + ry * 4) def render_shape(self, drawer, _, **kwargs): # draw background self.render_shape_background(drawer, **kwargs) if not kwargs.get('shadow') and self.node.background: drawer.image(self.textbox, self.node.background) def render_shape_background(self, drawer, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) pt = m.topleft rx = (self.node.width or self.metrics.node_width) // 12 ry = (self.node.height or self.metrics.node_height) // 5 ellipses = [Box(pt.x + rx * 2, pt.y + ry, pt.x + rx * 5, pt.y + ry * 3), Box(pt.x + rx * 4, pt.y, pt.x + rx * 9, pt.y + ry * 2), Box(pt.x + rx * 8, pt.y + ry, pt.x + rx * 11, pt.y + ry * 3), Box(pt.x + rx * 9, pt.y + ry * 2, pt.x + rx * 13, pt.y + ry * 4), Box(pt.x + rx * 8, pt.y + ry * 2, pt.x + rx * 11, pt.y + ry * 5), Box(pt.x + rx * 5, pt.y + ry * 2, pt.x + rx * 8, pt.y + ry * 5), Box(pt.x + rx * 2, pt.y + ry * 2, pt.x + rx * 5, pt.y + ry * 5), Box(pt.x + rx * 0, pt.y + ry * 2, pt.x + rx * 4, pt.y + ry * 4)] for e in ellipses: if kwargs.get('shadow'): e = self.shift_shadow(e) if kwargs.get('style') == 'blur': drawer.ellipse(e, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(e, fill=fill, outline=fill) else: drawer.ellipse(e, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) rects = [Box(pt.x + rx * 2, pt.y + ry * 2, pt.x + rx * 11, pt.y + ry * 4), Box(pt.x + rx * 4, pt.y + ry, pt.x + rx * 9, pt.y + ry * 2)] for rect in rects: if kwargs.get('shadow'): rect = self.shift_shadow(rect) if kwargs.get('style') == 'blur': drawer.rectangle(rect, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(rect, fill=fill, outline=fill) else: drawer.rectangle(rect, fill=self.node.color, outline=self.node.color) def render_vector_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # create pathdata m = self.metrics.cell(self.node) rx = (self.node.width or self.metrics.node_width) // 12 ry = (self.node.height or self.metrics.node_height) // 5 pt = m.topleft if kwargs.get('shadow'): pt = self.shift_shadow(pt) path = pathdata(pt.x + rx * 2, pt.y + ry * 2) path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 4, pt.y + ry) path.ellarc(rx * 2, ry * 3 // 4, 0, 0, 1, pt.x + rx * 9, pt.y + ry) path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 11, pt.y + ry * 2) path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 11, pt.y + ry * 4) path.ellarc(rx * 2, ry * 5 // 2, 0, 0, 1, pt.x + rx * 8, pt.y + ry * 4) path.ellarc(rx * 2, ry * 5 // 2, 0, 0, 1, pt.x + rx * 5, pt.y + ry * 4) path.ellarc(rx * 2, ry * 5 // 2, 0, 0, 1, pt.x + rx * 2, pt.y + ry * 4) path.ellarc(rx * 2, ry, 0, 0, 1, pt.x + rx * 2, pt.y + ry * 2) # draw outline if kwargs.get('shadow'): if kwargs.get('style') == 'blur': drawer.path(path, fill=fill, outline=fill, filter='transp-blur') else: drawer.path(path, fill=fill, outline=fill) elif self.node.background: drawer.path(path, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.path(path, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('cloud', Cloud) blockdiag-1.5.3/src/blockdiag/noderenderer/diamond.py0000644000076600000240000000510512351302326023405 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box, XY class Diamond(NodeShape): def __init__(self, node, metrics=None): super(Diamond, self).__init__(node, metrics) r = metrics.cellsize m = metrics.cell(node) self.connectors = [XY(m.top.x, m.top.y - r), XY(m.right.x + r, m.right.y), XY(m.bottom.x, m.bottom.y + r), XY(m.left.x - r, m.left.y), XY(m.top.x, m.top.y - r)] self.textbox = Box((self.connectors[0].x + self.connectors[3].x) // 2, (self.connectors[0].y + self.connectors[3].y) // 2, (self.connectors[1].x + self.connectors[2].x) // 2, (self.connectors[1].y + self.connectors[2].y) // 2) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline if kwargs.get('shadow'): diamond = self.shift_shadow(self.connectors) if kwargs.get('style') == 'blur': drawer.polygon(diamond, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(diamond, fill=fill, outline=fill) elif self.node.background: drawer.polygon(self.connectors, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.polygon(self.connectors, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(self.connectors, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('diamond', Diamond) install_renderer('flowchart.condition', Diamond) blockdiag-1.5.3/src/blockdiag/noderenderer/dots.py0000644000076600000240000000331112351302332022735 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box, XY class Dots(NodeShape): def render_label(self, drawer, **kwargs): pass def render_shape(self, drawer, _, **kwargs): if kwargs.get('shadow'): return m = self.metrics center = m.cell(self.node).center dots = [center] if self.node.group.orientation == 'landscape': pt = XY(center.x, center.y - m.node_height / 2) dots.append(pt) pt = XY(center.x, center.y + m.node_height / 2) dots.append(pt) else: pt = XY(center.x - m.node_width / 3, center.y) dots.append(pt) pt = XY(center.x + m.node_width / 3, center.y) dots.append(pt) r = m.cellsize / 2 for dot in dots: box = Box(dot.x - r, dot.y - r, dot.x + r, dot.y + r) drawer.ellipse(box, fill=self.node.linecolor, outline=self.node.linecolor) def setup(self): install_renderer('dots', Dots) blockdiag-1.5.3/src/blockdiag/noderenderer/ellipse.py0000644000076600000240000000373712351302335023440 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box class Ellipse(NodeShape): def __init__(self, node, metrics=None): super(Ellipse, self).__init__(node, metrics) r = metrics.cellsize box = metrics.cell(node).box self.textbox = Box(box[0] + r, box[1] + r, box[2] - r, box[3] - r) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline box = self.metrics.cell(self.node).box if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(box, fill=fill, outline=fill) elif self.node.background: drawer.ellipse(box, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.ellipse(box, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.ellipse(box, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('ellipse', Ellipse) blockdiag-1.5.3/src/blockdiag/noderenderer/endpoint.py0000644000076600000240000000507212351302340023611 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import XY, Box class EndPoint(NodeShape): def __init__(self, node, metrics=None): super(EndPoint, self).__init__(node, metrics) m = metrics.cell(node) self.radius = metrics.cellsize self.center = m.center self.textbox = Box(m.top.x, m.top.y, m.right.x, m.right.y) self.textalign = 'left' self.connectors = [XY(self.center.x, self.center.y - self.radius), XY(self.center.x + self.radius, self.center.y), XY(self.center.x, self.center.y + self.radius), XY(self.center.x - self.radius, self.center.y)] def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outer circle r = self.radius box = Box(self.center.x - r, self.center.y - r, self.center.x + r, self.center.y + r) if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.ellipse(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(box, fill=fill, outline=fill) else: drawer.ellipse(box, fill='white', outline=self.node.linecolor, style=self.node.style) # draw inner circle box = Box(self.center.x - r / 2, self.center.y - r / 2, self.center.x + r / 2, self.center.y + r / 2) if not kwargs.get('shadow'): if self.node.color == self.node.basecolor: color = self.node.linecolor else: color = self.node.color drawer.ellipse(box, fill=color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('endpoint', EndPoint) blockdiag-1.5.3/src/blockdiag/noderenderer/flowchart/0000755000076600000240000000000012556430040023412 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/noderenderer/flowchart/__init__.py0000644000076600000240000000114411635000760025522 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. blockdiag-1.5.3/src/blockdiag/noderenderer/flowchart/database.py0000644000076600000240000001216312351302413025526 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import XY, Box from blockdiag.imagedraw.simplesvg import pathdata class Database(NodeShape): def __init__(self, node, metrics=None): super(Database, self).__init__(node, metrics) m = self.metrics.cell(self.node) r = self.metrics.cellsize self.textbox = Box(m.topleft.x, m.topleft.y + r * 3 // 2, m.bottomright.x, m.bottomright.y - r // 2) def render_shape(self, drawer, _, **kwargs): # draw background self.render_shape_background(drawer, **kwargs) # draw background image if self.node.background: drawer.image(self.textbox, self.node.background) def render_shape_background(self, drawer, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize box = m.box ellipse = Box(box[0], box[3] - r * 2, box[2], box[3]) if kwargs.get('shadow'): ellipse = self.shift_shadow(ellipse) if kwargs.get('style') == 'blur': drawer.ellipse(ellipse, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(ellipse, fill=fill, outline=fill) else: drawer.ellipse(ellipse, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) rect = Box(box[0], box[1] + r, box[2], box[3] - r) if kwargs.get('shadow'): rect = self.shift_shadow(rect) if kwargs.get('style') == 'blur': drawer.rectangle(rect, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(rect, fill=fill, outline=fill) else: drawer.rectangle(rect, fill=self.node.color, outline=self.node.color) ellipse = Box(box[0], box[1], box[2], box[1] + r * 2) if kwargs.get('shadow'): ellipse = self.shift_shadow(ellipse) if kwargs.get('style') == 'blur': drawer.ellipse(ellipse, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(ellipse, fill=fill, outline=fill) else: drawer.ellipse(ellipse, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # line both side lines = [(XY(box[0], box[1] + r), XY(box[0], box[3] - r)), (XY(box[2], box[1] + r), XY(box[2], box[3] - r))] for line in lines: if not kwargs.get('shadow'): drawer.line(line, fill=self.node.linecolor, style=self.node.style) def render_vector_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize width = self.metrics.node_width box = m.box if kwargs.get('shadow'): box = self.shift_shadow(box) path = pathdata(box[0], box[1] + r) path.ellarc(width // 2, r, 0, 0, 1, box[2], box[1] + r) path.line(box[2], box[3] - r) path.ellarc(width // 2, r, 0, 0, 1, box[0], box[3] - r) path.line(box[0], box[1] + r) # draw outline if kwargs.get('shadow'): if kwargs.get('style') == 'blur': drawer.path(path, fill=fill, outline=fill, filter='transp-blur') else: drawer.path(path, fill=fill, outline=fill) elif self.node.background: drawer.path(path, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.path(path, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # draw cap of cylinder if not kwargs.get('shadow'): path = pathdata(box[2], box[1] + r) path.ellarc(width // 2, r, 0, 0, 1, box[0], box[1] + r) drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.database', Database) blockdiag-1.5.3/src/blockdiag/noderenderer/flowchart/input.py0000644000076600000240000000453112351302416025124 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box, XY class Input(NodeShape): def __init__(self, node, metrics=None): super(Input, self).__init__(node, metrics) m = self.metrics.cell(self.node) r = self.metrics.cellsize * 3 self.textbox = Box(m.topleft.x + r, m.topleft.y, m.bottomright.x - r, m.bottomright.y) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 3 shape = [XY(m.topleft.x + r, m.topleft.y), XY(m.topright.x, m.topright.y), XY(m.bottomright.x - r, m.bottomright.y), XY(m.bottomleft.x, m.bottomleft.y), XY(m.topleft.x + r, m.topleft.y)] # draw outline if kwargs.get('shadow'): shape = self.shift_shadow(shape) if kwargs.get('style') == 'blur': drawer.polygon(shape, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(shape, fill=fill, outline=fill) elif self.node.background: drawer.polygon(shape, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.polygon(shape, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(shape, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.input', Input) blockdiag-1.5.3/src/blockdiag/noderenderer/flowchart/loopin.py0000644000076600000240000000505712351302422025266 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box, XY class LoopIn(NodeShape): def __init__(self, node, metrics=None): super(LoopIn, self).__init__(node, metrics) m = self.metrics.cell(self.node) ydiff = self.metrics.node_height // 4 self.textbox = Box(m.topleft.x, m.topleft.y + ydiff, m.bottomright.x, m.bottomright.y) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) xdiff = self.metrics.node_width // 4 ydiff = self.metrics.node_height // 4 shape = [XY(m.topleft.x + xdiff, m.topleft.y), XY(m.topright.x - xdiff, m.topleft.y), XY(m.topright.x, m.topright.y + ydiff), XY(m.topright.x, m.bottomright.y), XY(m.topleft.x, m.bottomleft.y), XY(m.topleft.x, m.topleft.y + ydiff), XY(m.topleft.x + xdiff, m.topleft.y)] # draw outline if kwargs.get('shadow'): shape = self.shift_shadow(shape) if kwargs.get('style') == 'blur': drawer.polygon(shape, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(shape, fill=fill, outline=fill) elif self.node.background: drawer.polygon(shape, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.polygon(shape, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(shape, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.loopin', LoopIn) blockdiag-1.5.3/src/blockdiag/noderenderer/flowchart/loopout.py0000644000076600000240000000507612351302425025473 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box, XY class LoopOut(NodeShape): def __init__(self, node, metrics=None): super(LoopOut, self).__init__(node, metrics) m = self.metrics.cell(self.node) ydiff = self.metrics.node_height // 4 self.textbox = Box(m.topleft.x, m.topleft.y, m.bottomright.x, m.bottomright.y - ydiff) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) xdiff = self.metrics.node_width // 4 ydiff = self.metrics.node_height // 4 shape = [XY(m.topleft.x, m.topleft.y), XY(m.topright.x, m.topright.y), XY(m.bottomright.x, m.bottomright.y - ydiff), XY(m.bottomright.x - xdiff, m.bottomright.y), XY(m.bottomleft.x + xdiff, m.bottomleft.y), XY(m.bottomleft.x, m.bottomleft.y - ydiff), XY(m.topleft.x, m.topleft.y)] # draw outline if kwargs.get('shadow'): shape = self.shift_shadow(shape) if kwargs.get('style') == 'blur': drawer.polygon(shape, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(shape, fill=fill, outline=fill) elif self.node.background: drawer.polygon(shape, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.polygon(shape, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(shape, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.loopout', LoopOut) blockdiag-1.5.3/src/blockdiag/noderenderer/flowchart/terminator.py0000644000076600000240000001071112351302436026150 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import XY, Box from blockdiag.imagedraw.simplesvg import pathdata class Terminator(NodeShape): def __init__(self, node, metrics=None): super(Terminator, self).__init__(node, metrics) m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 self.textbox = Box(m.topleft.x + r, m.topleft.y, m.bottomright.x - r, m.bottomright.y) def render_shape(self, drawer, _, **kwargs): # draw background self.render_shape_background(drawer, **kwargs) # draw outline if not kwargs.get('shadow') and self.node.background: drawer.image(self.textbox, self.node.background) def render_shape_background(self, drawer, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 box = m.box ellipses = [Box(box[0], box[1], box[0] + r * 2, box[3]), Box(box[2] - r * 2, box[1], box[2], box[3])] for e in ellipses: if kwargs.get('shadow'): e = self.shift_shadow(e) if kwargs.get('style') == 'blur': drawer.ellipse(e, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(e, fill=fill, outline=fill) else: drawer.ellipse(e, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) rect = Box(box[0] + r, box[1], box[2] - r, box[3]) if kwargs.get('shadow'): rect = self.shift_shadow(rect) if kwargs.get('style') == 'blur': drawer.rectangle(rect, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(rect, fill=fill, outline=fill) else: drawer.rectangle(rect, fill=self.node.color, outline=self.node.color) lines = [(XY(box[0] + r, box[1]), XY(box[2] - r, box[1])), (XY(box[0] + r, box[3]), XY(box[2] - r, box[3]))] for line in lines: if not kwargs.get('shadow'): drawer.line(line, fill=self.node.linecolor, style=self.node.style) def render_vector_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # create pathdata m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 height = self.metrics.node_height box = Box(m.topleft.x + r, m.topleft.y, m.bottomright.x - r, m.bottomright.y) if kwargs.get('shadow'): box = self.shift_shadow(box) path = pathdata(box[0], box[1]) path.line(box[2], box[1]) path.ellarc(r, height / 2, 0, 0, 1, box[2], box[3]) path.line(box[0], box[3]) path.ellarc(r, height / 2, 0, 0, 1, box[0], box[1]) # draw outline if kwargs.get('shadow'): if kwargs.get('style') == 'blur': drawer.path(path, fill=fill, outline=fill, filter='transp-blur') else: drawer.path(path, fill=fill, outline=fill) elif self.node.background: drawer.path(path, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.path(path, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('flowchart.terminator', Terminator) blockdiag-1.5.3/src/blockdiag/noderenderer/mail.py0000644000076600000240000000451712351302343022721 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box, XY class Mail(NodeShape): def __init__(self, node, metrics=None): super(Mail, self).__init__(node, metrics) m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 self.textbox = Box(m.topleft.x, m.topleft.y + r, m.bottomright.x, m.bottomright.y) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 # draw outline box = self.metrics.cell(self.node).box if kwargs.get('shadow'): box = self.shift_shadow(box) if kwargs.get('style') == 'blur': drawer.rectangle(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(box, fill=fill, outline=fill) elif self.node.background: drawer.rectangle(box, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.rectangle(box, outline=self.node.linecolor, style=self.node.style) else: drawer.rectangle(box, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # draw flap if not kwargs.get('shadow'): flap = [m.topleft, XY(m.top.x, m.top.y + r), m.topright] drawer.line(flap, fill=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('mail', Mail) blockdiag-1.5.3/src/blockdiag/noderenderer/minidiamond.py0000644000076600000240000000365212351302350024264 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box, XY class MiniDiamond(NodeShape): def __init__(self, node, metrics=None): super(MiniDiamond, self).__init__(node, metrics) r = metrics.cellsize m = metrics.cell(node) c = m.center self.connectors = (XY(c.x, c.y - r), XY(c.x + r, c.y), XY(c.x, c.y + r), XY(c.x - r, c.y), XY(c.x, c.y - r)) self.textbox = Box(m.top.x, m.top.y, m.right.x, m.right.y) self.textalign = 'left' def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline if kwargs.get('shadow'): diamond = self.shift_shadow(self.connectors) if kwargs.get('style') == 'blur': drawer.polygon(diamond, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(diamond, fill=fill, outline=fill) else: drawer.polygon(self.connectors, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('minidiamond', MiniDiamond) blockdiag-1.5.3/src/blockdiag/noderenderer/none.py0000644000076600000240000000212012351302354022724 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape class NoneShape(NodeShape): def __init__(self, node, metrics=None): super(NoneShape, self).__init__(node, metrics) p = metrics.cell(node).center self.connectors = [p, p, p, p] def render_label(self, drawer, **kwargs): pass def render_shape(self, drawer, _, **kwargs): pass def setup(self): install_renderer('none', NoneShape) blockdiag-1.5.3/src/blockdiag/noderenderer/note.py0000644000076600000240000000435612351302360022744 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import XY class Note(NodeShape): def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize * 2 tr = m.topright note = [m.topleft, XY(tr.x - r, tr.y), XY(tr.x, tr.y + r), m.bottomright, m.bottomleft, m.topleft] box = self.metrics.cell(self.node).box # draw outline if kwargs.get('shadow'): note = self.shift_shadow(note) if kwargs.get('style') == 'blur': drawer.polygon(note, fill=fill, outline=fill, filter='transp-blur') else: drawer.polygon(note, fill=fill, outline=fill) elif self.node.background: drawer.polygon(note, fill=self.node.color, outline=self.node.color) drawer.image(box, self.node.background) drawer.polygon(note, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.polygon(note, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) # draw folded if not kwargs.get('shadow'): folded = [XY(tr.x - r, tr.y), XY(tr.x - r, tr.y + r), XY(tr.x, tr.y + r)] drawer.line(folded, fill=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('note', Note) blockdiag-1.5.3/src/blockdiag/noderenderer/roundedbox.py0000644000076600000240000001226212351302364024147 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import XY, Box from blockdiag.imagedraw.simplesvg import pathdata class RoundedBox(NodeShape): def render_shape(self, drawer, _, **kwargs): # draw background self.render_shape_background(drawer, **kwargs) # draw outline box = self.metrics.cell(self.node).box if not kwargs.get('shadow'): if self.node.background: drawer.image(box, self.node.background) self.render_shape_outline(drawer, **kwargs) def render_shape_outline(self, drawer, **kwargs): m = self.metrics.cell(self.node) r = self.metrics.cellsize box = m.box lines = [(XY(box[0] + r, box[1]), XY(box[2] - r, box[1])), (XY(box[2], box[1] + r), XY(box[2], box[3] - r)), (XY(box[0] + r, box[3]), XY(box[2] - r, box[3])), (XY(box[0], box[1] + r), XY(box[0], box[3] - r))] for line in lines: drawer.line(line, fill=self.node.linecolor, style=self.node.style) r2 = r * 2 arcs = [(Box(box[0], box[1], box[0] + r2, box[1] + r2), 180, 270), (Box(box[2] - r2, box[1], box[2], box[1] + r2), 270, 360), (Box(box[2] - r2, box[3] - r2, box[2], box[3]), 0, 90), (Box(box[0], box[3] - r2, box[0] + r2, box[3]), 90, 180)] for arc in arcs: drawer.arc(arc[0], arc[1], arc[2], fill=self.node.linecolor, style=self.node.style) def render_shape_background(self, drawer, **kwargs): fill = kwargs.get('fill') m = self.metrics.cell(self.node) r = self.metrics.cellsize box = m.box ellipses = [Box(box[0], box[1], box[0] + r * 2, box[1] + r * 2), Box(box[2] - r * 2, box[1], box[2], box[1] + r * 2), Box(box[0], box[3] - r * 2, box[0] + r * 2, box[3]), Box(box[2] - r * 2, box[3] - r * 2, box[2], box[3])] for e in ellipses: if kwargs.get('shadow'): e = self.shift_shadow(e) if kwargs.get('style') == 'blur': drawer.ellipse(e, fill=fill, outline=fill, filter='transp-blur') else: drawer.ellipse(e, fill=fill, outline=fill) else: drawer.ellipse(e, fill=self.node.color, outline=self.node.color) rects = [Box(box[0] + r, box[1], box[2] - r, box[3]), Box(box[0], box[1] + r, box[2], box[3] - r)] for rect in rects: if kwargs.get('shadow'): rect = self.shift_shadow(rect) if kwargs.get('style') == 'blur': drawer.rectangle(rect, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(rect, fill=fill, outline=fill) else: drawer.rectangle(rect, fill=self.node.color, outline=self.node.color) def render_vector_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # create pathdata box = self.metrics.cell(self.node).box r = self.metrics.cellsize if kwargs.get('shadow'): box = self.shift_shadow(box) path = pathdata(box[0] + r, box[1]) path.line(box[2] - r, box[1]) path.ellarc(r, r, 0, 0, 1, box[2], box[1] + r) path.line(box[2], box[3] - r) path.ellarc(r, r, 0, 0, 1, box[2] - r, box[3]) path.line(box[0] + r, box[3]) path.ellarc(r, r, 0, 0, 1, box[0], box[3] - r) path.line(box[0], box[1] + r) path.ellarc(r, r, 0, 0, 1, box[0] + r, box[1]) # draw outline if kwargs.get('shadow'): if kwargs.get('style') == 'blur': drawer.path(path, fill=fill, outline=fill, filter='transp-blur') else: drawer.path(path, fill=fill, outline=fill) elif self.node.background: drawer.path(path, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.path(path, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.path(path, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('roundedbox', RoundedBox) blockdiag-1.5.3/src/blockdiag/noderenderer/square.py0000644000076600000240000000453712351302367023307 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import Box, XY class Square(NodeShape): def __init__(self, node, metrics=None): super(Square, self).__init__(node, metrics) r = min(metrics.node_width, metrics.node_height) // 2 + \ metrics.cellsize // 2 pt = metrics.cell(node).center self.connectors = [XY(pt.x, pt.y - r), # top XY(pt.x + r, pt.y), # right XY(pt.x, pt.y + r), # bottom XY(pt.x - r, pt.y)] # left self.textbox = Box(pt.x - r, pt.y - r, pt.x + r, pt.y + r) def render_shape(self, drawer, _, **kwargs): fill = kwargs.get('fill') # draw outline if kwargs.get('shadow'): box = self.shift_shadow(self.textbox) if kwargs.get('style') == 'blur': drawer.rectangle(box, fill=fill, outline=fill, filter='transp-blur') else: drawer.rectangle(box, fill=fill, outline=fill) elif self.node.background: drawer.rectangle(self.textbox, fill=self.node.color, outline=self.node.color) drawer.image(self.textbox, self.node.background) drawer.rectangle(self.textbox, fill="none", outline=self.node.linecolor, style=self.node.style) else: drawer.rectangle(self.textbox, fill=self.node.color, outline=self.node.linecolor, style=self.node.style) def setup(self): install_renderer('square', Square) blockdiag-1.5.3/src/blockdiag/noderenderer/textbox.py0000644000076600000240000000343512351302376023500 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division from blockdiag.noderenderer import install_renderer from blockdiag.noderenderer.base import NodeShape from blockdiag.utils import images, Box, XY class TextBox(NodeShape): def __init__(self, node, metrics=None): super(TextBox, self).__init__(node, metrics) if self.node.background: size = images.get_image_size(self.node.background) size = images.calc_image_size(size, self.textbox.size) pt = self.textbox.center self.textbox = Box(pt.x - size[0] // 2, pt.y - size[1] // 2, pt.x + size[0] // 2, pt.y + size[1] // 2) self.connectors[0] = XY(pt.x, self.textbox[1]) self.connectors[1] = XY(self.textbox[2], pt.y) self.connectors[2] = XY(pt.x, self.textbox[3]) self.connectors[3] = XY(self.textbox[0], pt.y) if self.node.icon: self.connectors[3] = XY(self.iconbox[0], pt.y) def render_shape(self, drawer, _, **kwargs): if not kwargs.get('shadow') and self.node.background: drawer.image(self.textbox, self.node.background) def setup(self): install_renderer('textbox', TextBox) blockdiag-1.5.3/src/blockdiag/parser.py0000644000076600000240000001670112543521511020620 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2008/2009 Andrey Vlasovskikh # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. r'''A DOT language parser using funcparserlib. The parser is based on [the DOT grammar][1]. It is pretty complete with a few not supported things: * Ports and compass points * XML identifiers At the moment, the parser builds only a parse tree, not an abstract syntax tree (AST) or an API for dealing with DOT. [1]: http://www.graphviz.org/doc/info/lang.html ''' import io from re import MULTILINE, DOTALL from collections import namedtuple from funcparserlib.lexer import make_tokenizer, Token, LexerError from funcparserlib.parser import (some, a, maybe, many, finished, skip, forward_decl) from blockdiag.utils.compat import u ENCODING = 'utf-8' Diagram = namedtuple('Diagram', 'id stmts') Group = namedtuple('Group', 'id stmts') Node = namedtuple('Node', 'id attrs') Attr = namedtuple('Attr', 'name value') Edge = namedtuple('Edge', 'from_nodes edge_type to_nodes attrs') Extension = namedtuple('Extension', 'type name attrs') Statements = namedtuple('Statements', 'stmts') class ParseException(Exception): pass def tokenize(string): """str -> Sequence(Token)""" # flake8: NOQA specs = [ # NOQA ('Comment', (r'/\*(.|[\r\n])*?\*/', MULTILINE)), # NOQA ('Comment', (r'(//|#).*',)), # NOQA ('NL', (r'[\r\n]+',)), # NOQA ('Space', (r'[ \t\r\n]+',)), # NOQA ('Name', (u('[A-Za-z_0-9\u0080-\uffff]') + # NOQA u('[A-Za-z_\\-.0-9\u0080-\uffff]*'),)), # NOQA ('Op', (r'[{};,=\[\]]|(<->)|(<-)|(--)|(->)|(>-<)|(-<)|(>-)',)), # NOQA ('Number', (r'-?(\.[0-9]+)|([0-9]+(\.[0-9]*)?)',)), # NOQA ('String', (r'(?P"|\').*?(? object""" tokval = lambda x: x.value op = lambda s: a(Token('Op', s)) >> tokval op_ = lambda s: skip(op(s)) _id = some(lambda t: t.type in ['Name', 'Number', 'String']) >> tokval keyword = lambda s: a(Token('Name', s)) >> tokval def make_node_list(node_list, attrs): return Statements([Node(node, attrs) for node in node_list]) def make_edge(first, edge_type, second, followers, attrs): edges = [Edge(first, edge_type, second, attrs)] from_node = second for edge_type, to_node in followers: edges.append(Edge(from_node, edge_type, to_node, attrs)) from_node = to_node return Statements(edges) # # parts of syntax # node_list = ( _id + many(op_(',') + _id) >> create_mapper(oneplus_to_list) ) option_stmt = ( _id + maybe(op_('=') + _id) >> create_mapper(Attr) ) option_list = ( maybe(op_('[') + option_stmt + many(op_(',') + option_stmt) + op_(']')) >> create_mapper(oneplus_to_list, default_value=[]) ) # node (node list) statement:: # A; # B [attr = value, attr = value]; # C, D [attr = value, attr = value]; # node_stmt = ( node_list + option_list >> create_mapper(make_node_list) ) # edge statement:: # A -> B; # A <- B; # edge_relation = ( op('->') | op('--') | op('<-') | op('<->') | op('>-') | op('-<') | op('>-<') ) edge_stmt = ( node_list + edge_relation + node_list + many(edge_relation + node_list) + option_list >> create_mapper(make_edge) ) # attributes statement:: # default_shape = box; # default_fontsize = 16; # attribute_stmt = ( _id + op_('=') + _id >> create_mapper(Attr) ) # extension statement (class, plugin):: # class red [color = red]; # plugin attributes [name = Name]; # extension_stmt = ( (keyword('class') | keyword('plugin')) + _id + option_list >> create_mapper(Extension) ) # group statement:: # group { # A; # } # group_stmt = forward_decl() group_inline_stmt = ( edge_stmt | group_stmt | attribute_stmt | node_stmt ) group_inline_stmt_list = ( many(group_inline_stmt + skip(maybe(op(';')))) ) group_stmt.define( skip(keyword('group')) + maybe(_id) + op_('{') + group_inline_stmt_list + op_('}') >> create_mapper(Group) ) # # diagram statement:: # blockdiag { # A; # } # diagram_id = ( (keyword('diagram') | keyword('blockdiag')) + maybe(_id) >> list ) diagram_inline_stmt = ( extension_stmt | group_inline_stmt ) diagram_inline_stmt_list = ( many(diagram_inline_stmt + skip(maybe(op(';')))) ) diagram = ( maybe(diagram_id) + op_('{') + diagram_inline_stmt_list + op_('}') >> create_mapper(Diagram) ) dotfile = diagram + skip(finished) return dotfile.parse(seq) def sort_tree(tree): def weight(node): if isinstance(node, (Attr, Extension)): return 1 else: return 2 if hasattr(tree, 'stmts'): tree.stmts.sort(key=lambda x: weight(x)) for stmt in tree.stmts: sort_tree(stmt) return tree def parse_string(string): try: tree = parse(tokenize(string)) return sort_tree(tree) except LexerError as e: message = "Got unexpected token at line %d column %d" % e.place raise ParseException(message) except Exception as e: raise ParseException(str(e)) def parse_file(path): code = io.open(path, 'r', encoding='utf-8-sig').read() return parse_string(code) blockdiag-1.5.3/src/blockdiag/plugins/0000755000076600000240000000000012556430040020426 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/plugins/__init__.py0000644000076600000240000000513412431606533022546 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from pkg_resources import iter_entry_points from blockdiag.utils.logging import warning loaded_plugins = [] node_handlers = [] general_handlers = {} def load(plugins, diagram, **kwargs): for name in plugins: if name in loaded_plugins: warning('plugin "%s" is already loaded. ignored.', name) return for ep in iter_entry_points('blockdiag_plugins', name): module = ep.load() loaded_plugins.append(name) if hasattr(module, 'setup'): module.setup(module, diagram, **kwargs) break else: msg = "unknown plugin: %s" % name raise AttributeError(msg) def install_general_handler(name, handler): if name not in general_handlers: general_handlers[name] = [] general_handlers[name].append(handler) def fire_general_event(name, *args): handlers = general_handlers.get(name, []) return all(handler(*args) for handler in handlers) def install_node_handler(handler): if handler not in node_handlers: node_handlers.append(handler) def fire_node_event(node, name, *args): return all(handler.fire(name, node, *args) for handler in node_handlers) class NodeHandler(object): def __init__(self, diagram, **kwargs): self.diagram = diagram self.config = kwargs.get('config') def fire(self, name, *args): return getattr(self, "on_" + name)(*args) def on_created(self, node): return True def on_attr_changing(self, node, attr): return True def on_attr_changed(self, node, attr): return True def on_build_finished(self, node): return True def cleanup(): fire_general_event('cleanup') for name in list(general_handlers.keys()): del general_handlers[name] for handler in node_handlers[:]: node_handlers.remove(handler) for plugin in loaded_plugins[:]: loaded_plugins.remove(plugin) def setup(app): app.register_cleanup_handler(cleanup) blockdiag-1.5.3/src/blockdiag/plugins/attributes.py0000644000076600000240000000241212417166553023200 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from blockdiag import plugins class NodeAttributes(plugins.NodeHandler): def __init__(self, diagram, **kwargs): super(NodeAttributes, self).__init__(diagram, **kwargs) node_klass = diagram._DiagramNode for name, label in kwargs.items(): if label is None: label = name if name not in node_klass.desctable: node_klass.desctable.insert(-1, name) node_klass.attrname[name] = label if not hasattr(node_klass, name): setattr(node_klass, name, None) def setup(self, diagram, **kwargs): plugins.install_node_handler(NodeAttributes(diagram, **kwargs)) blockdiag-1.5.3/src/blockdiag/plugins/autoclass.py0000644000076600000240000000220412360215311022767 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re from blockdiag import plugins class AutoClass(plugins.NodeHandler): def on_created(self, node): if node.id is None: return True for name, klass in self.diagram.classes.items(): pattern = "_%s$" % re.escape(name) if re.search(pattern, node.id): node.label = re.sub(pattern, '', node.id) node.set_attributes(klass.attrs) return True def setup(self, diagram, **kwargs): plugins.install_node_handler(AutoClass(diagram, **kwargs)) blockdiag-1.5.3/src/blockdiag/tests/0000755000076600000240000000000012556430040020107 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/tests/__init__.py0000644000076600000240000000003012516723414022217 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- blockdiag-1.5.3/src/blockdiag/tests/diagrams/0000755000076600000240000000000012556430040021676 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/tests/diagrams/auto_jumping_edge.diag0000644000076600000240000000004312227701012026201 0ustar tkomiyastaff00000000000000{ A -> B -> C, D; A, C -> E; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/background_url_image.diag0000644000076600000240000000047312350173505026675 0ustar tkomiyastaff00000000000000{ A [background = "http://python.org/images/python-logo.gif"]; B [background = "http://blockdiag.com/favicon.ico"]; C [background = "http://upload.wikimedia.org/wikipedia/commons/9/9b/Scalable_Vector_Graphics_Circle2.svg"]; D [background = "http://people.sc.fsu.edu/~jburkardt/data/eps/circle.eps"]; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/beginpoint_color.diag0000644000076600000240000000005412227701020026051 0ustar tkomiyastaff00000000000000{ A [shape = beginpoint, color = pink]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/branched.diag0000644000076600000240000000005612227702770024302 0ustar tkomiyastaff00000000000000diagram { A -> B -> C; A -> D -> E; Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/circular_ref.diag0000644000076600000240000000006112227702770025170 0ustar tkomiyastaff00000000000000diagram { A -> B -> C -> B B -> D Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/circular_ref2.diag0000644000076600000240000000005212217213441025241 0ustar tkomiyastaff00000000000000{ A -> B -> C -> D, E -> F -> C; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/circular_ref_and_parent_node.diag0000644000076600000240000000005212227701036030362 0ustar tkomiyastaff00000000000000{ A -> B, C; D -> B -> C -> D; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/circular_ref_to_root.diag0000644000076600000240000000006112227702770026735 0ustar tkomiyastaff00000000000000diagram { A -> B -> C -> A B -> D Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/circular_skipped_edge.diag0000644000076600000240000000006112227702770027037 0ustar tkomiyastaff00000000000000diagram { A -> B -> C -> A A -> C Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/debian-logo-256color-palettealpha.png0000644000076600000240000000073212360215311030574 0ustar tkomiyastaff00000000000000PNG  IHDR00*lgAMA asRGB cHRMz&u0`:pQ< PLTE5u(tRNS@fbKGDf |d pHYs."."ݒIDAT(u= P8CwUʟM($)<$Tg'b(D@沏B2Z90Ϟ Q}C;Wb' 4/6&)y_{$0[լuQ ~.`%]>I*mwyW}GJOn%tEXtdate:create2013-01-08T12:01:27+09:00%tEXtdate:modify2012-12-26T10:09:41+09:00%jIENDB`blockdiag-1.5.3/src/blockdiag/tests/diagrams/define_class.diag0000644000076600000240000000017212227701045025144 0ustar tkomiyastaff00000000000000{ class emphasis [style = dashed, color = red]; A -> B -> C; A -> B [class = emphasis]; A [class = emphasis]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/diagram_attributes.diag0000644000076600000240000000054512344514622026406 0ustar tkomiyastaff00000000000000blockdiag { node_width = 160; node_height = 160; span_width = 32; span_height = 32; default_fontsize = 16; default_shape = diamond default_node_style = dotted default_node_color = red default_group_color = blue default_linecolor = gray default_textcolor = green default_label_orientation = vertical A -> B; group { B; } } blockdiag-1.5.3/src/blockdiag/tests/diagrams/diagram_attributes_order.diag0000644000076600000240000000010512227701052027564 0ustar tkomiyastaff00000000000000{ A; default_node_color = red; default_linecolor = red; B; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/diagram_orientation.diag0000644000076600000240000000010212227701053026534 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B -> C; B -> D; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/edge_attribute.diag0000644000076600000240000000011712227702770025521 0ustar tkomiyastaff00000000000000diagram { A -> B -> C [color = red] D -> E [dir = none] F -> G [thick] } blockdiag-1.5.3/src/blockdiag/tests/diagrams/edge_datamodels.diag0000644000076600000240000000003112227701060025615 0ustar tkomiyastaff00000000000000{ A >- B >-< C -< D; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/edge_label.diag0000644000076600000240000000004512227701062024566 0ustar tkomiyastaff00000000000000{ A -> B [label = "test label"]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/edge_layout_landscape.diag0000644000076600000240000000010412227701064027034 0ustar tkomiyastaff00000000000000{ edge_layout = flowchart; A [shape = diamond]; A -> B, C; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/edge_layout_portrait.diag0000644000076600000240000000013512227701066026754 0ustar tkomiyastaff00000000000000{ orientation = portrait edge_layout = flowchart; A [shape = diamond]; A -> B, C; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/edge_shape.diag0000644000076600000240000000003612227701070024606 0ustar tkomiyastaff00000000000000{ A -- B -> C <- D <-> E; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/edge_styles.diag0000644000076600000240000000035112227702770025041 0ustar tkomiyastaff00000000000000diagram { A -> B [style = "none"]; B -> C [style = "solid"]; C -> D [style = "dashed"]; D -> E [style = "dotted"]; E -> F [hstyle = "generalization"]; F -> H [hstyle = "composition"]; H -> I [hstyle = "aggregation"]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/empty_group.diag0000644000076600000240000000003612227702770025104 0ustar tkomiyastaff00000000000000diagram { group { } Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/empty_group_declaration.diag0000644000076600000240000000010612227701075027444 0ustar tkomiyastaff00000000000000{ group foo { color = red; } group foo { A; } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/empty_nested_group.diag0000644000076600000240000000006112227702770026444 0ustar tkomiyastaff00000000000000diagram { group { group { } } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/endpoint_color.diag0000644000076600000240000000005212227701104025534 0ustar tkomiyastaff00000000000000{ A [shape = endpoint, color = pink]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/0000755000076600000240000000000012556430040023212 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/belongs_to_two_groups.diag0000644000076600000240000000007012227702770030467 0ustar tkomiyastaff00000000000000diagram { group { A } group { A } Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/group_follows_node.diag0000644000076600000240000000005112227702770027751 0ustar tkomiyastaff00000000000000diagram { A -> group { B } Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/lexer_error.diag0000644000076600000240000000001412227701474026372 0ustar tkomiyastaff00000000000000{ A - B } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/node_follows_group.diag0000644000076600000240000000005112227702770027751 0ustar tkomiyastaff00000000000000diagram { A -> group { B } Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_diagram_default_shape.diag0000644000076600000240000000006712227701500032107 0ustar tkomiyastaff00000000000000{ default_shape = "test_unknown_shape"; A; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_diagram_edge_layout.diag0000644000076600000240000000003512227701503031602 0ustar tkomiyastaff00000000000000{ edge_layout = unknown; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_diagram_orientation.diag0000644000076600000240000000003512227701505031636 0ustar tkomiyastaff00000000000000{ orientation = unknown; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_diagram_type.diag0000644000076600000240000000002012251537642030264 0ustar tkomiyastaff00000000000000unknown { A } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_edge_class.diag0000644000076600000240000000004012227701507027706 0ustar tkomiyastaff00000000000000{ A -> B [class = unknown]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_edge_dir.diag0000644000076600000240000000003612227701511027357 0ustar tkomiyastaff00000000000000{ A -> B [dir = unknown]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_edge_hstyle.diag0000644000076600000240000000004112217213441030104 0ustar tkomiyastaff00000000000000{ A -> B [hstyle = unknown]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_edge_style.diag0000644000076600000240000000004012217213441027733 0ustar tkomiyastaff00000000000000{ A -> B [style = unknown]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_group_class.diag0000644000076600000240000000005612217213441030137 0ustar tkomiyastaff00000000000000{ group { class = unknown; A; } } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_group_orientation.diag0000644000076600000240000000006412217213441031364 0ustar tkomiyastaff00000000000000{ group { orientation = unknown; A; } } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_group_shape.diag0000644000076600000240000000010112217213441030121 0ustar tkomiyastaff00000000000000{ group { shape = "test_unknown_shape"; A; } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_node_attribute.diag0000644000076600000240000000004412217213441030623 0ustar tkomiyastaff00000000000000{ A [unknown_attribute = True]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_node_class.diag0000644000076600000240000000003312217213441027723 0ustar tkomiyastaff00000000000000{ A [class = unknown]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_node_shape.diag0000644000076600000240000000005612217213441027723 0ustar tkomiyastaff00000000000000{ A [shape = "test_unknown_shape"]; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_node_style.diag0000644000076600000240000000003312217213441027756 0ustar tkomiyastaff00000000000000{ A [style = unknown]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/errors/unknown_plugin.diag0000644000076600000240000000004312217213441027110 0ustar tkomiyastaff00000000000000{ plugin unknown_plugin; A; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/flowable_node.diag0000644000076600000240000000004212227702770025327 0ustar tkomiyastaff00000000000000diagram { B -> C A -> B Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/folded_edge.diag0000644000076600000240000000011512227702770024751 0ustar tkomiyastaff00000000000000diagram { A -> B -> C [nofolded] B -> D -> E[folded] D -> F Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_and_skipped_edge.diag0000644000076600000240000000010512227701112027175 0ustar tkomiyastaff00000000000000{ A -> B -> C -> D; A -> E -> D; Z; group { B; C; } } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_attribute.diag0000644000076600000240000000014112227701113025734 0ustar tkomiyastaff00000000000000{ group { color = "red"; label = "group label"; shape = "line"; A; } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_children_height.diag0000644000076600000240000000012112227701115027051 0ustar tkomiyastaff00000000000000{ group { A -> B; A -> C; A -> D; } B -> E; D -> F; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_children_order.diag0000644000076600000240000000013212227701120026712 0ustar tkomiyastaff00000000000000{ group { A -> B; A -> C; A -> D; } D -> G; B -> E; C -> F; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_children_order2.diag0000644000076600000240000000014512227701122027002 0ustar tkomiyastaff00000000000000{ group { A -> B; A -> C; A -> D; } A -> F; D -> G; B -> E; C -> F; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_children_order3.diag0000644000076600000240000000017412227701124027007 0ustar tkomiyastaff00000000000000{ group { A -> B; A -> C; A -> D; } group { Q; } D -> G; B -> E; C -> F; Q -> F; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_children_order4.diag0000644000076600000240000000014712227701126027012 0ustar tkomiyastaff00000000000000diagram { A -> B, C, D -> E; Z; group A { A } group B { B } group C { C } group D { D } } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_declare_as_node_attribute.diag0000644000076600000240000000013512227701130031105 0ustar tkomiyastaff00000000000000{ C [group = foo]; D [group = foo]; A -> B -> C; D; group foo { E; } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_height.diag0000644000076600000240000000011412227701131025201 0ustar tkomiyastaff00000000000000{ group { B; C; D; } A -> B -> C; B -> D; A -> E; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_id_and_node_id_are_not_conflicted.diag0000644000076600000240000000006412227702770032547 0ustar tkomiyastaff00000000000000diagram { A -> B group B { C -> D } Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_label.diag0000644000076600000240000000006412227701134025017 0ustar tkomiyastaff00000000000000{ group { label = "test label"; A; } } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_order.diag0000644000076600000240000000007712227701136025061 0ustar tkomiyastaff00000000000000diagram{ A; B; C; group { B; } A -> B; A -> C; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_order2.diag0000644000076600000240000000014012227701140025125 0ustar tkomiyastaff00000000000000{ A -> B; A -> C -> D; A -> E -> F; Z; group { C; D } group { E; F } } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_order3.diag0000644000076600000240000000017012227701141025132 0ustar tkomiyastaff00000000000000diagram admin { A; group { B; C; D; } E; Z; A -> B; A -> E; B -> C -> B; B -> D -> B; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_orientation.diag0000644000076600000240000000012512227701145026273 0ustar tkomiyastaff00000000000000{ A -> B; Z; group { orientation = portrait B -> C; B -> D; } } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_sibling.diag0000644000076600000240000000010412227701147025366 0ustar tkomiyastaff00000000000000{ A -> B, C; B -> D, E; group { C -> F; } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/group_works_node_decorator.diag0000644000076600000240000000012212227702770030156 0ustar tkomiyastaff00000000000000diagram { A -> B -> C A -> B -> D A -> E group { A; B; D; E } Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/labeled_circular_ref.diag0000644000076600000240000000013012227701153026627 0ustar tkomiyastaff00000000000000{ A [label = "foo"]; B [label = "bar"]; C [label = "baz"]; A -> C -> B -> C; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/large_group_and_node.diag0000644000076600000240000000012312227702770026664 0ustar tkomiyastaff00000000000000diagram { group { A -> B A -> C A -> D A -> E } B -> F Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/large_group_and_node2.diag0000644000076600000240000000007412227702770026753 0ustar tkomiyastaff00000000000000diagram { group { A -> B -> C } C -> D -> E Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/large_group_and_two_nodes.diag0000644000076600000240000000013412227702770027742 0ustar tkomiyastaff00000000000000diagram { group { A -> B A -> C A -> D A -> E } B -> F C -> G Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/merge_groups.diag0000644000076600000240000000010512227701161025216 0ustar tkomiyastaff00000000000000{ group hoge{ A -> B; } group hoge{ C -> D; } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/multiple_groups.diag0000644000076600000240000000021312227702770025761 0ustar tkomiyastaff00000000000000diagram { group { A; B; C; D } group { E; F; G } group { H; I } group { J } A -> E -> H -> J Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/multiple_nested_groups.diag0000644000076600000240000000015312227702770027326 0ustar tkomiyastaff00000000000000diagram { group { A -> B; A -> C; group { B } group { C } } Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/multiple_node_relation.diag0000644000076600000240000000003312227701170027255 0ustar tkomiyastaff00000000000000{ A -> B, C -> D; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/multiple_nodes_definition.diag0000644000076600000240000000003712227701171027760 0ustar tkomiyastaff00000000000000{ A, B [color = red]; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/multiple_parent_node.diag0000644000076600000240000000004712227701203026733 0ustar tkomiyastaff00000000000000{ A -> B; C -> D; E -> B; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/nested_group_orientation.diag0000644000076600000240000000016612227701206027640 0ustar tkomiyastaff00000000000000{ group { group { orientation = portrait A -> B; group { C; } } } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/nested_group_orientation2.diag0000644000076600000240000000020412227701207027714 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B; group { orientation = portrait C -> D; group { E -> F; } } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/nested_groups.diag0000644000076600000240000000010112227702770025404 0ustar tkomiyastaff00000000000000diagram { group { A; group { B; } } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/nested_groups_and_edges.diag0000644000076600000240000000012112227702770027377 0ustar tkomiyastaff00000000000000diagram { A -> B -> C; group { B; group { C; } } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/nested_groups_work_node_decorator.diag0000644000076600000240000000010712227702770031523 0ustar tkomiyastaff00000000000000diagram { A; B; group { A group { B } } Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/nested_skipped_circular.diag0000644000076600000240000000016012227702770027415 0ustar tkomiyastaff00000000000000diagram { A -> B -> F -> G B -> C -> E -> F C -> D -> E F -> A Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_attribute.diag0000644000076600000240000000070312360215311025527 0ustar tkomiyastaff00000000000000diagram { A [label="B", color="red"]; B [label="double quoted"]; C [label='single quoted', color = 'red']; D [label="'\"double\" quoted'", color = 'red']; E [label='"\'single\' quoted"', color = 'red', numbered = "1"]; F [textcolor = red]; G [stacked]; H [fontsize = 16]; I [linecolor = red]; J [label="Hello", label_orientation=vertical]; K [background = "src/blockdiag/tests/diagrams/debian-logo-256color-palettealpha.png"]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_attribute_and_group.diag0000644000076600000240000000026012227701222027566 0ustar tkomiyastaff00000000000000diagram { A [label = "foo", color = "red"]; B [label = "bar", color = "#888888"]; C [label = "baz", color = "blue"]; A -> B -> C; group { A; B; } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_has_multilined_label.diag0000644000076600000240000000004212227701223027664 0ustar tkomiyastaff00000000000000{ A [label="foo bar"]; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_height.diag0000644000076600000240000000006112227701224024776 0ustar tkomiyastaff00000000000000{ A -> B -> C; B -> D; A -> E; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_icon.diag0000644000076600000240000000025512310534756024472 0ustar tkomiyastaff00000000000000{ A -> B; A [label = "aaaaaaaaaaaaaaaaa", icon = "/usr/share/pixmaps/debian-logo.png"]; B [label = "aaaaaaaaaaaaaaaaa", icon = "http://blockdiag.com/favicon.ico"]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_id_includes_dot.diag0000644000076600000240000000002712227701227026663 0ustar tkomiyastaff00000000000000{ A.B -> C.D; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_in_group_follows_outer_node.diag0000644000076600000240000000006212227702770031350 0ustar tkomiyastaff00000000000000diagram { group { A -> B } B -> C Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_link.diag0000644000076600000240000000022312227701233024463 0ustar tkomiyastaff00000000000000{ A [href = "http://www.yahoo.co.jp/"]; B [href = "http://www.yahoo.co.jp/"]; group { href = "http://www.disney.co.jp"; C; D; } } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_rotated_labels.diag0000644000076600000240000000014512227701234026516 0ustar tkomiyastaff00000000000000{ A [rotate = 0]; B [rotate = 90]; C [rotate = 180]; D [rotate = 270]; E [rotate = 360]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_shape.diag0000644000076600000240000000114712227701236024637 0ustar tkomiyastaff00000000000000{ A [shape = "box"]; B [shape = "roundedbox"]; C [shape = "diamond"]; D [shape = "ellipse"]; E [shape = "note"]; F [shape = "cloud"]; G [shape = "mail"]; H [shape = "beginpoint"]; I [shape = "endpoint"]; J [shape = "minidiamond"]; K [shape = "flowchart.condition"]; L [shape = "flowchart.database"]; M [shape = "flowchart.input"]; N [shape = "flowchart.loopin"]; O [shape = "flowchart.loopout"]; P [shape = "actor"]; Q [shape = "flowchart.terminator"]; R [shape = "textbox"]; S [shape = "dots"]; T [shape = "none"]; U [shape = "square"]; V [shape = "circle"]; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_shape_background.diag0000644000076600000240000000344112227701237027036 0ustar tkomiyastaff00000000000000{ A [shape = "box", background = "src/blockdiag/tests/diagrams/white.gif"]; B [shape = "roundedbox", background = "src/blockdiag/tests/diagrams/white.gif"]; C [shape = "diamond", background = "src/blockdiag/tests/diagrams/white.gif"]; D [shape = "ellipse", background = "src/blockdiag/tests/diagrams/white.gif"]; E [shape = "note", background = "src/blockdiag/tests/diagrams/white.gif"]; F [shape = "cloud", background = "src/blockdiag/tests/diagrams/white.gif"]; G [shape = "mail", background = "src/blockdiag/tests/diagrams/white.gif"]; H [shape = "beginpoint", background = "src/blockdiag/tests/diagrams/white.gif"]; I [shape = "endpoint", background = "src/blockdiag/tests/diagrams/white.gif"]; J [shape = "minidiamond", background = "src/blockdiag/tests/diagrams/white.gif"]; K [shape = "flowchart.condition", background = "src/blockdiag/tests/diagrams/white.gif"]; L [shape = "flowchart.database", background = "src/blockdiag/tests/diagrams/white.gif"]; M [shape = "flowchart.input", background = "src/blockdiag/tests/diagrams/white.gif"]; N [shape = "flowchart.loopin", background = "src/blockdiag/tests/diagrams/white.gif"]; O [shape = "flowchart.loopout", background = "src/blockdiag/tests/diagrams/white.gif"]; P [shape = "actor", background = "src/blockdiag/tests/diagrams/white.gif"]; Q [shape = "flowchart.terminator", background = "src/blockdiag/tests/diagrams/white.gif"]; R [shape = "textbox", background = "src/blockdiag/tests/diagrams/white.gif"]; S [shape = "dots", background = "src/blockdiag/tests/diagrams/white.gif"]; T [shape = "none", background = "src/blockdiag/tests/diagrams/white.gif"]; U [shape = "square", background = "src/blockdiag/tests/diagrams/white.gif"]; V [shape = "circle", background = "src/blockdiag/tests/diagrams/white.gif"]; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_shape_namespace.diag0000644000076600000240000000015412227701241026644 0ustar tkomiyastaff00000000000000{ shape_namespace = "flowchart"; A [shape = "flowchart.condition"]; B [shape = "condition"]; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_shape_with_none_shadow.diag0000644000076600000240000000120112227701254030245 0ustar tkomiyastaff00000000000000{ shadow_style = "none"; A [shape = "box"]; B [shape = "roundedbox"]; C [shape = "diamond"]; D [shape = "ellipse"]; E [shape = "note"]; F [shape = "cloud"]; G [shape = "mail"]; H [shape = "beginpoint"]; I [shape = "endpoint"]; J [shape = "minidiamond"]; K [shape = "flowchart.condition"]; L [shape = "flowchart.database"]; M [shape = "flowchart.input"]; N [shape = "flowchart.loopin"]; O [shape = "flowchart.loopout"]; P [shape = "actor"]; Q [shape = "flowchart.terminator"]; R [shape = "textbox"]; S [shape = "dots"]; T [shape = "none"]; U [shape = "square"]; V [shape = "circle"]; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_shape_with_solid_shadow.diag0000644000076600000240000000120212217213441030414 0ustar tkomiyastaff00000000000000{ shadow_style = "solid"; A [shape = "box"]; B [shape = "roundedbox"]; C [shape = "diamond"]; D [shape = "ellipse"]; E [shape = "note"]; F [shape = "cloud"]; G [shape = "mail"]; H [shape = "beginpoint"]; I [shape = "endpoint"]; J [shape = "minidiamond"]; K [shape = "flowchart.condition"]; L [shape = "flowchart.database"]; M [shape = "flowchart.input"]; N [shape = "flowchart.loopin"]; O [shape = "flowchart.loopout"]; P [shape = "actor"]; Q [shape = "flowchart.terminator"]; R [shape = "textbox"]; S [shape = "dots"]; T [shape = "none"]; U [shape = "square"]; V [shape = "circle"]; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_style_dasharray.diag0000644000076600000240000000024712227701260026732 0ustar tkomiyastaff00000000000000{ A [style = "2,2,4,2"]; B [shape = diamond, style = "2,2,4,2"]; C [shape = ellipse, style = "2,2,4,2"]; D [shape = flowchart.database, style = "2,2,4,2"]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_styles.diag0000644000076600000240000000023312227702770025061 0ustar tkomiyastaff00000000000000diagram { A [shape = "roundedbox", style = "dotted"]; B [shape = "ellipse", style = "dashed"]; C [shape = "flowchart.database", style = "dashed"]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/node_width_and_height.diag0000644000076600000240000000007012227701263027022 0ustar tkomiyastaff00000000000000{ A -> B, C; A [height = 80]; C [width = 256]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/non_rhombus_relation_height.diag0000644000076600000240000000011612227701264030304 0ustar tkomiyastaff00000000000000{ A -> B -> C; D; E -> F, G, J; G -> H, I; J -> K; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/outer_node_follows_node_in_group.diag0000644000076600000240000000006212227702770031350 0ustar tkomiyastaff00000000000000diagram { group { B -> C } A -> B Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/plugin_attributes.diag0000644000076600000240000000021012227701267026270 0ustar tkomiyastaff00000000000000{ plugin attributes [test_attr1, test_attr2 = name, test_attr3 = name]; A [test_attr1 = 1, test_attr2 = 2, test_attr3 = 3]; B; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/plugin_autoclass.diag0000644000076600000240000000017012227701271026100 0ustar tkomiyastaff00000000000000{ plugin autoclass; class emphasis [style = dashed, color = red]; A_emphasis -> B_emphasis; A_emphasis -> C; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/portrait_dots.diag0000644000076600000240000000006312227701273025424 0ustar tkomiyastaff00000000000000{ orientation = portrait; A [shape = dots]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/quoted_node_id.diag0000644000076600000240000000005212227701274025510 0ustar tkomiyastaff00000000000000{ A -> "A" -> 'A' -> "'A'" -> B; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/README0000644000076600000240000000106212360215311022550 0ustar tkomiyastaff00000000000000Copyrights of images ===================== debian-logo-256color-palettealpha.png -------------------------------------- The Debian Open Use Logo(s) are Copyright (c) 1999 Software in the Public Interest, Inc., and are released under the terms of the GNU Lesser General Public License, version 3 or any later version, or, at your option, of the Creative Commons Attribution-ShareAlike 3.0 Unported License. it is modified format:: $ convert debian-logo-original.png -type PaletteAlpha -colors 256 -transparent #000000 debian-logo-256color-palettealpha.png blockdiag-1.5.3/src/blockdiag/tests/diagrams/reverse_multiple_groups.diag0000644000076600000240000000021312227702770027514 0ustar tkomiyastaff00000000000000diagram { group { A; B; C; D } group { E; F; G } group { H; I } group { J } J -> H -> E -> A Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/rhombus_relation_height.diag0000644000076600000240000000004312227701277027435 0ustar tkomiyastaff00000000000000{ A -> B, C -> D -> E, F; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/self_ref.diag0000644000076600000240000000003612227702770024317 0ustar tkomiyastaff00000000000000diagram { A -> B -> B Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/separate1.diag0000644000076600000240000000025212227701302024405 0ustar tkomiyastaff00000000000000{ A -> B -> C -> D; group outer { label = "outer" B; D; group inner { label = "inner" color = skyblue; C -> E -> F; } } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/separate2.diag0000644000076600000240000000033512227701303024411 0ustar tkomiyastaff00000000000000diagram { A -> B; A -> C; E -> F; C -> G; D -> H; group outer { label = "group 1"; B -> E -> C; group inner { label = "sub group 1"; color = skyblue C -> D; } } Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/simple_group.diag0000644000076600000240000000006412227702770025240 0ustar tkomiyastaff00000000000000diagram { group { A -> B A -> C } Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/single_edge.diag0000644000076600000240000000002612227702770024776 0ustar tkomiyastaff00000000000000diagram { A -> B; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/single_node.diag0000644000076600000240000000002112227702770025012 0ustar tkomiyastaff00000000000000diagram { A; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_circular.diag0000644000076600000240000000006512227702770026057 0ustar tkomiyastaff00000000000000diagram { A -> C A -> B -> C C -> A Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge.diag0000644000076600000240000000005412227702770025155 0ustar tkomiyastaff00000000000000diagram { A -> B -> C A -> C Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_down.diag0000644000076600000240000000004212227701315026173 0ustar tkomiyastaff00000000000000{ A; B; C; A -> C [folded]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_flowchart_rightdown.diag0000644000076600000240000000010212227701316031300 0ustar tkomiyastaff00000000000000{ edge_layout = flowchart; A -> B, C; C -> D; A -> D; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_flowchart_rightdown2.diag0000644000076600000240000000010312227701320031356 0ustar tkomiyastaff00000000000000{ edge_layout = flowchart; A; B -> C; A -> C [folded]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_leftdown.diag0000644000076600000240000000007112227701321027045 0ustar tkomiyastaff00000000000000{ A -> B -> C, D; E; F -> G; C -> G [folded]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_portrait_down.diag0000644000076600000240000000007012227701323030117 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B -> C; A -> C; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_portrait_flowchart_rightdown.diag0000644000076600000240000000013312227701331033225 0ustar tkomiyastaff00000000000000{ orientation = portrait; edge_layout = flowchart; A -> B, C; C -> D; A -> D; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_portrait_flowchart_rightdown2.diag0000644000076600000240000000013512227701332033312 0ustar tkomiyastaff00000000000000{ orientation = portrait; edge_layout = flowchart; A; B -> C; A -> C [folded]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_portrait_leftdown.diag0000644000076600000240000000007312227701333030776 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B -> C; D -> C, E; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_portrait_right.diag0000644000076600000240000000007512217213441030270 0ustar tkomiyastaff00000000000000{ orientation = portrait; A; B; C; A -> C [folded]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_portrait_rightdown.diag0000644000076600000240000000011212227701340031151 0ustar tkomiyastaff00000000000000{ orientation = portrait; A -> B, C; B -> D; C -> E; A -> E; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_right.diag0000644000076600000240000000003512227701342026343 0ustar tkomiyastaff00000000000000{ A -> B -> C; A -> C; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_rightdown.diag0000644000076600000240000000004412227701343027234 0ustar tkomiyastaff00000000000000{ A -> B -> C, D; A -> C, D; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_rightup.diag0000644000076600000240000000004012217213441026701 0ustar tkomiyastaff00000000000000{ A -> B -> C; D -> C, E; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_edge_up.diag0000644000076600000240000000004312227701357025657 0ustar tkomiyastaff00000000000000{ A; B; C; C -> A [folded]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/skipped_twin_circular.diag0000644000076600000240000000011312217213441027101 0ustar tkomiyastaff00000000000000{ A -> B -> E -> B; B -> C -> E; B -> D -> E; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/slided_children.diag0000644000076600000240000000013012217213441025630 0ustar tkomiyastaff00000000000000{ C; F; A -> B -> C; B -> G -> H; B -> F -> H; A -> D -> E -> F -> H; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/stacked_node_and_edges.diag0000644000076600000240000000006112217213441027133 0ustar tkomiyastaff00000000000000{ A -> B; A -> C [folded]; A [stacked]; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/triple_branched.diag0000644000076600000240000000005312227702770025656 0ustar tkomiyastaff00000000000000diagram { A -> D B -> D C -> D Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/twin_circular_ref.diag0000644000076600000240000000006112217213441026220 0ustar tkomiyastaff00000000000000{ A -> B -> C -> B; A -> D -> C -> D; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/twin_circular_ref_to_root.diag0000644000076600000240000000005412227702770030000 0ustar tkomiyastaff00000000000000diagram { A -> B -> A A -> C -> A Z } blockdiag-1.5.3/src/blockdiag/tests/diagrams/twin_forked.diag0000644000076600000240000000006612217213441025037 0ustar tkomiyastaff00000000000000{ A -> B, C; B -> D -> E, F; C, F -> G; Z; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/twin_multiple_parent_node.diag0000644000076600000240000000006112217213441027771 0ustar tkomiyastaff00000000000000{ A -> B; Z; E -> D; C -> D; C -> B; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/two_edges.diag0000644000076600000240000000003312227702770024507 0ustar tkomiyastaff00000000000000diagram { A -> B -> C; } blockdiag-1.5.3/src/blockdiag/tests/diagrams/white.gif0000644000076600000240000000005312217213441023501 0ustar tkomiyastaff00000000000000GIF89a!,D;blockdiag-1.5.3/src/blockdiag/tests/rst/0000755000076600000240000000000012556430040020717 5ustar tkomiyastaff00000000000000blockdiag-1.5.3/src/blockdiag/tests/rst/__init__.py0000644000076600000240000000003012516723414023027 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- blockdiag-1.5.3/src/blockdiag/tests/rst/test_base_directives.py0000644000076600000240000001126512472051114025466 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- import io import os import sys from docutils import nodes from docutils.core import publish_doctree from docutils.parsers.rst import directives as docutils from blockdiag.utils.rst import directives from blockdiag.utils.rst.nodes import blockdiag as blockdiag_node from blockdiag.tests.utils import capture_stderr, TemporaryDirectory if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class TestRstDirectives(unittest.TestCase): def setUp(self): docutils.register_directive('blockdiag', directives.BlockdiagDirectiveBase) self._tmpdir = TemporaryDirectory() def tearDown(self): if 'blockdiag' in docutils._directives: del docutils._directives['blockdiag'] self._tmpdir.clean() @capture_stderr def test_without_args(self): text = ".. blockdiag::" doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.system_message, type(doctree[0])) def test_block(self): text = (".. blockdiag::\n" "\n" " { A -> B }") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(blockdiag_node, type(doctree[0])) self.assertEqual('{ A -> B }', doctree[0]['code']) self.assertEqual(None, doctree[0]['alt']) self.assertEqual({}, doctree[0]['options']) @capture_stderr def test_emptyblock(self): text = ".. blockdiag::\n\n \n" text = (".. blockdiag::\n" "\n" " ") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.system_message, type(doctree[0])) def test_filename(self): dirname = os.path.dirname(__file__) filename = os.path.join(dirname, '../diagrams/node_attribute.diag') text = ".. blockdiag:: %s" % filename doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(blockdiag_node, type(doctree[0])) self.assertEqual(io.open(filename, encoding='utf-8-sig').read(), doctree[0]['code']) self.assertEqual(None, doctree[0]['alt']) self.assertEqual({}, doctree[0]['options']) @capture_stderr def test_filename_not_exists(self): text = ".. blockdiag:: unknown.diag" doctree = publish_doctree(text) self.assertEqual(nodes.system_message, type(doctree[0])) @capture_stderr def test_both_block_and_filename(self): text = (".. blockdiag:: unknown.diag\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.system_message, type(doctree[0])) def test_full_options(self): text = (".. blockdiag::\n" " :alt: hello world\n" " :align: center\n" " :desctable:\n" " :width: 200\n" " :height: 100\n" " :scale: 50%\n" " :name: foo\n" " :class: bar\n" " :figwidth: 400\n" " :figclass: baz\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(blockdiag_node, type(doctree[0])) self.assertEqual('A -> B', doctree[0]['code']) self.assertEqual('hello world', doctree[0]['alt']) self.assertEqual('center', doctree[0]['options']['align']) self.assertEqual(None, doctree[0]['options']['desctable']) self.assertEqual('200', doctree[0]['options']['width']) self.assertEqual('100', doctree[0]['options']['height']) self.assertEqual(50, doctree[0]['options']['scale']) self.assertEqual('hello world', doctree[0]['options']['alt']) self.assertEqual('foo', doctree[0]['options']['name']) self.assertEqual(['bar'], doctree[0]['options']['classes']) self.assertEqual('400px', doctree[0]['options']['figwidth']) self.assertEqual(['baz'], doctree[0]['options']['figclass']) @capture_stderr def test_maxwidth_option(self): text = (".. blockdiag::\n" " :maxwidth: 200\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(2, len(doctree)) self.assertEqual(blockdiag_node, type(doctree[0])) self.assertEqual('A -> B', doctree[0]['code']) self.assertEqual('200', doctree[0]['options']['width']) self.assertEqual(nodes.system_message, type(doctree[1])) blockdiag-1.5.3/src/blockdiag/tests/rst/test_blockdiag_directives.py0000644000076600000240000010245512472051057026503 0ustar tkomiyastaff00000000000000# -*- coding: utf-8 -*- import os import sys from docutils import nodes from docutils.core import publish_doctree from docutils.parsers.rst import directives as docutils from blockdiag.utils.compat import u from blockdiag.utils.rst import directives from blockdiag.tests.utils import capture_stderr, with_pil, TemporaryDirectory if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class TestRstDirectives(unittest.TestCase): def setUp(self): self._tmpdir = TemporaryDirectory() def tearDown(self): if 'blockdiag' in docutils._directives: del docutils._directives['blockdiag'] self._tmpdir.clean() @property def tmpdir(self): return self._tmpdir.name def test_setup(self): directives.setup() options = directives.directive_options self.assertIn('blockdiag', docutils._directives) self.assertEqual(directives.BlockdiagDirective, docutils._directives['blockdiag']) self.assertEqual('PNG', options['format']) self.assertEqual(False, options['antialias']) self.assertEqual(None, options['fontpath']) self.assertEqual(False, options['nodoctype']) self.assertEqual(False, options['noviewbox']) self.assertEqual(False, options['inline_svg']) def test_setup_with_args(self): directives.setup(format='SVG', antialias=True, fontpath='/dev/null', nodoctype=True, noviewbox=True, inline_svg=True) options = directives.directive_options self.assertIn('blockdiag', docutils._directives) self.assertEqual(directives.BlockdiagDirective, docutils._directives['blockdiag']) self.assertEqual('SVG', options['format']) self.assertEqual(True, options['antialias']) self.assertEqual('/dev/null', options['fontpath']) self.assertEqual(True, options['nodoctype']) self.assertEqual(True, options['noviewbox']) self.assertEqual(True, options['inline_svg']) @capture_stderr def test_cleanup(self): directives.setup(format='SVG', outputdir=self.tmpdir, noviewbox=False) text = (".. blockdiag::\n" "\n" " plugin autoclass\n" " A -> B") publish_doctree(text) from blockdiag import plugins self.assertEqual([], plugins.loaded_plugins) def test_setup_fontpath1(self): with self.assertRaises(RuntimeError): directives.setup(format='SVG', fontpath=['dummy.ttf'], outputdir=self.tmpdir) text = (".. blockdiag::\n" "\n" " A -> B") publish_doctree(text) def test_setup_fontpath2(self): with self.assertRaises(RuntimeError): directives.setup(format='SVG', fontpath='dummy.ttf', outputdir=self.tmpdir) text = (".. blockdiag::\n" "\n" " A -> B") publish_doctree(text) def test_setup_nodoctype_is_true(self): directives.setup(format='SVG', outputdir=self.tmpdir, nodoctype=True) text = (".. blockdiag::\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[-1])) svg = open(doctree[0]['uri']).read() self.assertEqual(" B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) svg = open(doctree[0]['uri']).read() self.assertEqual("\n" " B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) svg = open(doctree[0]['uri']).read() self.assertRegexpMatches(svg, ' B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) svg = open(doctree[0]['uri']).read() self.assertRegexpMatches(svg, ' B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.raw, type(doctree[0])) self.assertEqual('html', doctree[0]['format']) self.assertEqual(nodes.Text, type(doctree[0][0])) self.assertEqual("\n" " B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(1, len(os.listdir(self.tmpdir))) @with_pil def test_setup_inline_svg_is_true_but_format_isnt_svg(self): directives.setup(format='PNG', outputdir=self.tmpdir, inline_svg=True) text = (".. blockdiag::\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) def test_setup_inline_svg_is_true_with_multibytes(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = u(".. blockdiag::\n" "\n" " あ -> い") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) def test_setup_inline_svg_is_true_and_width_option1(self): directives.setup(format='SVG', outputdir=self.tmpdir, nodoctype=True, noviewbox=True, inline_svg=True) text = (".. blockdiag::\n" " :width: 100\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.raw, type(doctree[0])) self.assertEqual(nodes.Text, type(doctree[0][0])) self.assertRegexpMatches(doctree[0][0], ' B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.raw, type(doctree[0])) self.assertEqual(nodes.Text, type(doctree[0][0])) self.assertRegexpMatches(doctree[0][0], ' B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.raw, type(doctree[0])) self.assertEqual(nodes.Text, type(doctree[0][0])) self.assertRegexpMatches(doctree[0][0], ' B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.raw, type(doctree[0])) self.assertEqual(nodes.Text, type(doctree[0][0])) self.assertRegexpMatches(doctree[0][0], ' B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.raw, type(doctree[0])) self.assertEqual(nodes.Text, type(doctree[0][0])) self.assertRegexpMatches(doctree[0][0], ' B" " }") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_call_without_braces(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_alt_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :alt: hello world\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual('hello world', doctree[0]['alt']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_align_option1(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :align: left\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual('left', doctree[0]['align']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_align_option2(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :align: center\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual('center', doctree[0]['align']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_align_option3(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :align: right\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual('right', doctree[0]['align']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) @capture_stderr def test_align_option4(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :align: unknown\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.system_message, type(doctree[0])) # clear stderr outputs (ignore ERROR) from io import StringIO sys.stderr = StringIO() def test_caption_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :caption: hello world\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.figure, type(doctree[0])) self.assertEqual(2, len(doctree[0])) self.assertEqual(nodes.image, type(doctree[0][0])) self.assertEqual(nodes.caption, type(doctree[0][1])) self.assertEqual(1, len(doctree[0][1])) self.assertEqual(nodes.Text, type(doctree[0][1][0])) self.assertEqual('hello world', doctree[0][1][0]) def test_caption_option2(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :caption: **hello** *world*\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.figure, type(doctree[0])) self.assertEqual(2, len(doctree[0])) self.assertEqual(nodes.image, type(doctree[0][0])) self.assertEqual(nodes.caption, type(doctree[0][1])) self.assertEqual(3, len(doctree[0][1])) self.assertEqual(nodes.strong, type(doctree[0][1][0])) self.assertEqual('hello', doctree[0][1][0][0]) self.assertEqual(nodes.Text, type(doctree[0][1][1])) self.assertEqual(' ', doctree[0][1][1][0]) self.assertEqual(nodes.emphasis, type(doctree[0][1][2])) self.assertEqual('world', doctree[0][1][2][0]) def test_caption_option_and_align_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :align: left\n" " :caption: hello world\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.figure, type(doctree[0])) self.assertEqual('left', doctree[0]['align']) self.assertEqual(2, len(doctree[0])) self.assertEqual(nodes.image, type(doctree[0][0])) self.assertNotIn('align', doctree[0][0]) self.assertEqual(nodes.caption, type(doctree[0][1])) self.assertEqual(1, len(doctree[0][1])) self.assertEqual(nodes.Text, type(doctree[0][1][0])) self.assertEqual('hello world', doctree[0][1][0]) @capture_stderr def test_maxwidth_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :maxwidth: 100\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(2, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual('100', doctree[0]['width']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) self.assertEqual(nodes.system_message, type(doctree[1])) def test_width_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :width: 100\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual('100', doctree[0]['width']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_height_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :height: 100\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual('100', doctree[0]['height']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_scale_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :scale: 50%\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(50, doctree[0]['scale']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_name_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :name: foo%\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(['foo%'], doctree[0]['names']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_class_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :class: bar%\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(['bar'], doctree[0]['classes']) self.assertEqual(0, doctree[0]['uri'].index(self.tmpdir)) def test_figwidth_option1(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :caption: hello world\n" " :figwidth: 100\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.figure, type(doctree[0])) self.assertEqual('100px', doctree[0]['width']) def test_figwidth_option2(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :caption: hello world\n" " :figwidth: image\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.figure, type(doctree[0])) self.assertEqual('448px', doctree[0]['width']) def test_figclass_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :caption: hello world\n" " :figclass: baz\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.figure, type(doctree[0])) self.assertEqual(['baz'], doctree[0]['classes']) def test_desctable_option(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :desctable:\n" "\n" " A [description = foo]" " B [description = bar]" " group { A }") doctree = publish_doctree(text) self.assertEqual(2, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(nodes.table, type(doctree[1])) # tgroup self.assertEqual(4, len(doctree[1][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][1])) self.assertEqual(nodes.thead, type(doctree[1][0][2])) self.assertEqual(nodes.tbody, type(doctree[1][0][3])) # colspec self.assertEqual(50, doctree[1][0][0]['colwidth']) self.assertEqual(50, doctree[1][0][1]['colwidth']) # thead thead = doctree[1][0][2] self.assertEqual(2, len(thead[0])) self.assertEqual('Name', thead[0][0][0][0]) self.assertEqual('Description', thead[0][1][0][0]) # tbody tbody = doctree[1][0][3] self.assertEqual(2, len(tbody)) self.assertEqual('A', tbody[0][0][0][0]) self.assertEqual('foo', tbody[0][1][0][0]) self.assertEqual('B', tbody[1][0][0][0]) self.assertEqual('bar', tbody[1][1][0][0]) def test_desctable_option_without_description(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :desctable:\n" "\n" " A -> B") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) def test_desctable_option_using_node_group(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :desctable:\n" "\n" " A [description = foo]" " B [description = bar]" " group { A }") doctree = publish_doctree(text) self.assertEqual(2, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(nodes.table, type(doctree[1])) self.assertEqual(1, len(doctree[1])) self.assertEqual(nodes.tgroup, type(doctree[1][0])) # tgroup self.assertEqual(4, len(doctree[1][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][1])) self.assertEqual(nodes.thead, type(doctree[1][0][2])) self.assertEqual(nodes.tbody, type(doctree[1][0][3])) # colspec self.assertEqual(0, len(doctree[1][0][0])) self.assertEqual(50, doctree[1][0][0]['colwidth']) self.assertEqual(0, len(doctree[1][0][1])) self.assertEqual(50, doctree[1][0][1]['colwidth']) # thead thead = doctree[1][0][2] self.assertEqual(1, len(thead)) self.assertEqual(2, len(thead[0])) self.assertEqual(1, len(thead[0][0])) self.assertEqual(1, len(thead[0][0][0])) self.assertEqual('Name', thead[0][0][0][0]) self.assertEqual(1, len(thead[0][1])) self.assertEqual(1, len(thead[0][1][0])) self.assertEqual('Description', thead[0][1][0][0]) # tbody tbody = doctree[1][0][3] self.assertEqual(2, len(tbody)) self.assertEqual(2, len(tbody[0])) self.assertEqual(1, len(tbody[0][0])) self.assertEqual(1, len(tbody[0][0][0])) self.assertEqual('A', tbody[0][0][0][0]) self.assertEqual(1, len(tbody[0][1])) self.assertEqual('foo', tbody[0][1][0][0]) self.assertEqual(2, len(tbody[1])) self.assertEqual(1, len(tbody[1][0])) self.assertEqual(1, len(tbody[1][0][0])) self.assertEqual('B', tbody[1][0][0][0]) self.assertEqual(1, len(tbody[1][1])) self.assertEqual('bar', tbody[1][1][0][0]) def test_desctable_option_with_rest_markups(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :desctable:\n" "\n" " A [description = \"foo *bar* **baz**\"]" " B [description = \"**foo** *bar* baz\"]") doctree = publish_doctree(text) self.assertEqual(2, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(nodes.table, type(doctree[1])) # tgroup self.assertEqual(4, len(doctree[1][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][1])) self.assertEqual(nodes.thead, type(doctree[1][0][2])) self.assertEqual(nodes.tbody, type(doctree[1][0][3])) # colspec self.assertEqual(50, doctree[1][0][0]['colwidth']) self.assertEqual(50, doctree[1][0][1]['colwidth']) # thead thead = doctree[1][0][2] self.assertEqual(2, len(thead[0])) self.assertEqual('Name', thead[0][0][0][0]) self.assertEqual('Description', thead[0][1][0][0]) # tbody tbody = doctree[1][0][3] self.assertEqual(2, len(tbody)) self.assertEqual('A', tbody[0][0][0][0]) self.assertEqual(4, len(tbody[0][1][0])) self.assertEqual(nodes.Text, type(tbody[0][1][0][0])) self.assertEqual('foo ', str(tbody[0][1][0][0])) self.assertEqual(nodes.emphasis, type(tbody[0][1][0][1])) self.assertEqual(nodes.Text, type(tbody[0][1][0][1][0])) self.assertEqual('bar', tbody[0][1][0][1][0]) self.assertEqual(nodes.Text, type(tbody[0][1][0][2])) self.assertEqual(' ', str(tbody[0][1][0][2])) self.assertEqual(nodes.strong, type(tbody[0][1][0][3])) self.assertEqual(nodes.Text, type(tbody[0][1][0][3][0])) self.assertEqual('baz', str(tbody[0][1][0][3][0])) self.assertEqual('B', tbody[1][0][0][0]) self.assertEqual(4, len(tbody[1][1][0])) self.assertEqual(nodes.strong, type(tbody[1][1][0][0])) self.assertEqual(nodes.Text, type(tbody[1][1][0][0][0])) self.assertEqual('foo', str(tbody[1][1][0][0][0])) self.assertEqual(nodes.Text, type(tbody[1][1][0][1])) self.assertEqual(' ', str(tbody[1][1][0][1])) self.assertEqual(nodes.emphasis, type(tbody[1][1][0][2])) self.assertEqual(nodes.Text, type(tbody[1][1][0][2][0])) self.assertEqual('bar', str(tbody[1][1][0][2][0])) self.assertEqual(nodes.Text, type(tbody[1][1][0][3])) self.assertEqual(' baz', str(tbody[1][1][0][3])) def test_desctable_option_with_numbered(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :desctable:\n" "\n" " A [numbered = 2]" " B [numbered = 1]") doctree = publish_doctree(text) self.assertEqual(2, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(nodes.table, type(doctree[1])) # tgroup self.assertEqual(4, len(doctree[1][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][1])) self.assertEqual(nodes.thead, type(doctree[1][0][2])) self.assertEqual(nodes.tbody, type(doctree[1][0][3])) # colspec self.assertEqual(25, doctree[1][0][0]['colwidth']) self.assertEqual(50, doctree[1][0][1]['colwidth']) # thead thead = doctree[1][0][2] self.assertEqual(2, len(thead[0])) self.assertEqual('No', thead[0][0][0][0]) self.assertEqual('Name', thead[0][1][0][0]) # tbody tbody = doctree[1][0][3] self.assertEqual(2, len(tbody)) self.assertEqual('1', tbody[0][0][0][0]) self.assertEqual('B', tbody[0][1][0][0]) self.assertEqual('2', tbody[1][0][0][0]) self.assertEqual('A', tbody[1][1][0][0]) def test_desctable_option_with_numbered_and_description(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :desctable:\n" "\n" " A [description = foo, numbered = 2]" " B [description = bar, numbered = 1]") doctree = publish_doctree(text) self.assertEqual(2, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(nodes.table, type(doctree[1])) # tgroup self.assertEqual(5, len(doctree[1][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][1])) self.assertEqual(nodes.colspec, type(doctree[1][0][2])) self.assertEqual(nodes.thead, type(doctree[1][0][3])) self.assertEqual(nodes.tbody, type(doctree[1][0][4])) # colspec self.assertEqual(25, doctree[1][0][0]['colwidth']) self.assertEqual(50, doctree[1][0][1]['colwidth']) self.assertEqual(50, doctree[1][0][2]['colwidth']) # thead thead = doctree[1][0][3] self.assertEqual(3, len(thead[0])) self.assertEqual('No', thead[0][0][0][0]) self.assertEqual('Name', thead[0][1][0][0]) self.assertEqual('Description', thead[0][2][0][0]) # tbody tbody = doctree[1][0][4] self.assertEqual(2, len(tbody)) self.assertEqual('1', tbody[0][0][0][0]) self.assertEqual('B', tbody[0][1][0][0]) self.assertEqual(1, len(tbody[0][2])) self.assertEqual('bar', tbody[0][2][0][0]) self.assertEqual('2', tbody[1][0][0][0]) self.assertEqual('A', tbody[1][1][0][0]) self.assertEqual('foo', tbody[1][2][0][0]) def test_desctable_option_for_edges(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :desctable:\n" "\n" " A -> B [description = \"foo\"]" " C -> D [description = \"bar\"]" " C [label = \"label_C\"]" " D [label = \"label_D\"]") doctree = publish_doctree(text) self.assertEqual(2, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(nodes.table, type(doctree[1])) # tgroup self.assertEqual(4, len(doctree[1][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][0])) self.assertEqual(nodes.colspec, type(doctree[1][0][1])) self.assertEqual(nodes.thead, type(doctree[1][0][2])) self.assertEqual(nodes.tbody, type(doctree[1][0][3])) # colspec self.assertEqual(25, doctree[1][0][0]['colwidth']) self.assertEqual(50, doctree[1][0][1]['colwidth']) # thead thead = doctree[1][0][2] self.assertEqual(2, len(thead[0])) self.assertEqual('Name', thead[0][0][0][0]) self.assertEqual('Description', thead[0][1][0][0]) # tbody tbody = doctree[1][0][3] self.assertEqual(2, len(tbody)) self.assertEqual('A -> B', tbody[0][0][0][0]) self.assertEqual(1, len(tbody[0][1][0])) self.assertEqual(nodes.Text, type(tbody[0][1][0][0])) self.assertEqual('foo', str(tbody[0][1][0][0])) self.assertEqual('label_C -> label_D', tbody[1][0][0][0]) self.assertEqual(1, len(tbody[1][1][0])) self.assertEqual(nodes.Text, type(tbody[1][1][0][0])) self.assertEqual('bar', str(tbody[1][1][0][0])) def test_desctable_option_for_nodes_and_edges(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" " :desctable:\n" "\n" " A -> B [description = \"foo\"]" " C -> D [description = \"bar\"]" " C [label = \"label_C\", description = foo]" " D [label = \"label_D\"]") doctree = publish_doctree(text) self.assertEqual(3, len(doctree)) self.assertEqual(nodes.image, type(doctree[0])) self.assertEqual(nodes.table, type(doctree[1])) self.assertEqual(nodes.table, type(doctree[2])) # tgroup self.assertEqual(4, len(doctree[2][0])) self.assertEqual(nodes.colspec, type(doctree[2][0][0])) self.assertEqual(nodes.colspec, type(doctree[2][0][1])) self.assertEqual(nodes.thead, type(doctree[2][0][2])) self.assertEqual(nodes.tbody, type(doctree[2][0][3])) # colspec self.assertEqual(25, doctree[2][0][0]['colwidth']) self.assertEqual(50, doctree[2][0][1]['colwidth']) # thead thead = doctree[2][0][2] self.assertEqual(2, len(thead[0])) self.assertEqual('Name', thead[0][0][0][0]) self.assertEqual('Description', thead[0][1][0][0]) # tbody tbody = doctree[2][0][3] self.assertEqual(2, len(tbody)) self.assertEqual('A -> B', tbody[0][0][0][0]) self.assertEqual(1, len(tbody[0][1][0])) self.assertEqual(nodes.Text, type(tbody[0][1][0][0])) self.assertEqual('foo', str(tbody[0][1][0][0])) self.assertEqual('label_C -> label_D', tbody[1][0][0][0]) self.assertEqual(1, len(tbody[1][1][0])) self.assertEqual(nodes.Text, type(tbody[1][1][0][0])) self.assertEqual('bar', str(tbody[1][1][0][0])) @capture_stderr def test_broken_diagram(self): directives.setup(format='SVG', outputdir=self.tmpdir) text = (".. blockdiag::\n" "\n" " A ->") doctree = publish_doctree(text) self.assertEqual(1, len(doctree)) self.assertEqual(nodes.system_message, type(doctree[0])) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_boot_params.py���������������������������������������������0000644�0000766�0000024�00000014072�12472051324�024033� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- import os import io import sys import tempfile from blockdiag.tests.utils import with_pdf import blockdiag from blockdiag.command import BlockdiagOptions from blockdiag.utils.bootstrap import detectfont from blockdiag.utils.compat import u if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class TestBootParams(unittest.TestCase): def setUp(self): self.parser = BlockdiagOptions(blockdiag) def test_type_option_svg(self): options = self.parser.parse(['-Tsvg', 'input.diag']) self.assertEqual(options.output, 'input.svg') options = self.parser.parse(['-TSVG', 'input.diag']) self.assertEqual(options.output, 'input.svg') options = self.parser.parse(['-TSvg', 'input.diag']) self.assertEqual(options.output, 'input.svg') options = self.parser.parse(['-TSvg', 'input.test.diag']) self.assertEqual(options.output, 'input.test.svg') def test_type_option_png(self): options = self.parser.parse(['-Tpng', 'input.diag']) self.assertEqual(options.output, 'input.png') @with_pdf def test_type_option_pdf(self): options = self.parser.parse(['-Tpdf', 'input.diag']) self.assertEqual(options.output, 'input.pdf') def test_invalid_type_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tsvgz', 'input.diag']) def test_separate_option_svg(self): self.parser.parse(['-Tsvg', '--separate', 'input.diag']) def test_separate_option_png(self): self.parser.parse(['-Tpng', '--separate', 'input.diag']) @with_pdf def test_separate_option_pdf(self): self.parser.parse(['-Tpdf', '--separate', 'input.diag']) def test_svg_nodoctype_option(self): self.parser.parse(['-Tsvg', '--nodoctype', 'input.diag']) def test_png_nodoctype_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tpng', '--nodoctype', 'input.diag']) def test_pdf_nodoctype_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tpdf', '--nodoctype', 'input.diag']) def test_svg_notransparency_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tsvg', '--no-transparency', 'input.diag']) def test_png_notransparency_option(self): self.parser.parse(['-Tpng', '--no-transparency', 'input.diag']) def test_pdf_notransparency_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-Tpdf', '--no-transparency', 'input.diag']) def test_config_option(self): try: tmp = tempfile.mkstemp() self.parser.parse(['-c', tmp[1], 'input.diag']) finally: os.close(tmp[0]) os.unlink(tmp[1]) def test_config_option_with_bom(self): try: tmp = tempfile.mkstemp() fp = io.open(tmp[0], 'wt', encoding='utf-8-sig') fp.write(u("[blockdiag]\n")) fp.close() self.parser.parse(['-c', tmp[1], 'input.diag']) finally: os.unlink(tmp[1]) def test_invalid_config_option(self): with self.assertRaises(RuntimeError): self.parser.parse(['-c', '/unknown_config_file', 'input.diag']) def test_invalid_dir_config_option(self): try: tmp = tempfile.mkdtemp() with self.assertRaises(RuntimeError): self.parser.parse(['-c', tmp, 'input.diag']) finally: os.rmdir(tmp) def test_config_option_fontpath(self): try: tmp = tempfile.mkstemp() config = u("[blockdiag]\nfontpath = /path/to/font\n") io.open(tmp[0], 'wt', encoding='utf-8-sig').write(config) options = self.parser.parse(['-c', tmp[1], 'input.diag']) self.assertEqual(options.font, ['/path/to/font']) finally: os.unlink(tmp[1]) def test_exist_font_config_option(self): try: fd, path = tempfile.mkstemp() os.close(fd) options = self.parser.parse(['-f', path, 'input.diag']) self.assertEqual(options.font, [path]) fontpath = detectfont(options) self.assertEqual(fontpath, path) finally: os.unlink(path) def test_not_exist_font_config_option(self): with self.assertRaises(RuntimeError): args = ['-f', '/font_is_not_exist', 'input.diag'] options = self.parser.parse(args) detectfont(options) def test_not_exist_font_config_option2(self): with self.assertRaises(RuntimeError): args = ['-f', '/font_is_not_exist', '-f', '/font_is_not_exist2', 'input.diag'] options = self.parser.parse(args) detectfont(options) def test_no_size_option(self): options = self.parser.parse(['input.diag']) self.assertEqual(None, options.size) def test_size_option(self): options = self.parser.parse(['--size', '480x360', 'input.diag']) self.assertEqual([480, 360], options.size) def test_invalid_size_option1(self): with self.assertRaises(RuntimeError): self.parser.parse(['--size', '480-360', 'input.diag']) def test_invalid_size_option2(self): with self.assertRaises(RuntimeError): self.parser.parse(['--size', '480', 'input.diag']) def test_invalid_size_option3(self): with self.assertRaises(RuntimeError): self.parser.parse(['--size', 'foobar', 'input.diag']) def test_not_exist_fontmap_config(self): with self.assertRaises(RuntimeError): args = ['--fontmap', '/fontmap_is_not_exist', 'input.diag'] options = self.parser.parse(args) fontpath = detectfont(options) self.assertTrue(fontpath) def test_unknown_image_driver(self): from blockdiag.drawer import DiagramDraw from blockdiag.elements import Diagram with self.assertRaises(RuntimeError): DiagramDraw('unknown', Diagram()) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_builder.py�������������������������������������������������0000644�0000766�0000024�00000016720�12344514622�023160� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- from blockdiag.tests.utils import BuilderTestCase class TestBuilder(BuilderTestCase): def test_diagram_attributes(self): diagram = self.build('diagram_attributes.diag') self.assertEqual(160, diagram.node_width) self.assertEqual(160, diagram.node_height) self.assertEqual(32, diagram.span_width) self.assertEqual(32, diagram.span_height) self.assertEqual((128, 128, 128), diagram.linecolor) # gray self.assertEqual('diamond', diagram.nodes[0].shape) self.assertEqual('dotted', diagram.nodes[0].style) self.assertEqual((255, 0, 0), diagram.nodes[0].color) # red self.assertEqual((0, 128, 0), diagram.nodes[0].textcolor) # green self.assertEqual(16, diagram.nodes[0].fontsize) self.assertEqual((0, 0, 255), diagram.nodes[1].color) # blue self.assertEqual((0, 128, 0), diagram.nodes[1].textcolor) # green self.assertEqual(16, diagram.nodes[1].fontsize) self.assertEqual((128, 128, 128), diagram.edges[0].color) # gray self.assertEqual((0, 128, 0), diagram.edges[0].textcolor) # green self.assertEqual(16, diagram.edges[0].fontsize) def test_diagram_attributes_order_diagram(self): diagram = self.build('diagram_attributes_order.diag') self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': (255, 0, 0)}) self.assertNodeLineColor(diagram, {'A': (255, 0, 0), 'B': (255, 0, 0)}) def test_circular_ref_to_root_diagram(self): diagram = self.build('circular_ref_to_root.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'Z': (0, 2)}) def test_circular_ref_diagram(self): diagram = self.build('circular_ref.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'Z': (0, 2)}) def test_circular_ref2_diagram(self): diagram = self.build('circular_ref2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (3, 0), 'E': (3, 1), 'F': (4, 0), 'Z': (0, 2)}) def test_circular_ref_and_parent_node_diagram(self): diagram = self.build('circular_ref_and_parent_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (2, 1), 'Z': (0, 2)}) def test_labeled_circular_ref_diagram(self): diagram = self.build('labeled_circular_ref.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (2, 0), 'C': (1, 0), 'Z': (0, 1)}) def test_twin_forked_diagram(self): diagram = self.build('twin_forked.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 2), 'D': (2, 0), 'E': (3, 0), 'F': (3, 1), 'G': (4, 1), 'Z': (0, 3)}) def test_skipped_edge_diagram(self): diagram = self.build('skipped_edge.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_circular_skipped_edge_diagram(self): diagram = self.build('circular_skipped_edge.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_triple_branched_diagram(self): diagram = self.build('triple_branched.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (1, 0), 'Z': (0, 3)}) def test_twin_circular_ref_to_root_diagram(self): diagram = self.build('twin_circular_ref_to_root.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}) def test_twin_circular_ref_diagram(self): diagram = self.build('twin_circular_ref.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (1, 1), 'Z': (0, 2)}) def test_skipped_circular_diagram(self): diagram = self.build('skipped_circular.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 1), 'C': (2, 0), 'Z': (0, 2)}) def test_skipped_twin_circular_diagram(self): diagram = self.build('skipped_twin_circular.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 1), 'D': (2, 2), 'E': (3, 0), 'Z': (0, 3)}) def test_nested_skipped_circular_diagram(self): diagram = self.build('nested_skipped_circular.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 1), 'D': (3, 2), 'E': (4, 1), 'F': (5, 0), 'G': (6, 0), 'Z': (0, 3)}) def test_self_ref_diagram(self): diagram = self.build('self_ref.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'Z': (0, 1)}) def test_diagram_orientation_diagram(self): diagram = self.build('diagram_orientation.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (1, 2), 'Z': (2, 0)}) def test_nested_group_orientation2_diagram(self): diagram = self.build('nested_group_orientation2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (1, 2), 'E': (2, 2), 'F': (2, 3), 'Z': (3, 0)}) def test_slided_children_diagram(self): diagram = self.build('slided_children.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (1, 3), 'E': (2, 3), 'F': (3, 2), 'G': (2, 1), 'H': (4, 1)}) def test_non_rhombus_relation_height_diagram(self): diagram = self.build('non_rhombus_relation_height.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (0, 1), 'E': (0, 2), 'F': (1, 2), 'G': (1, 3), 'H': (2, 3), 'I': (2, 4), 'J': (1, 5), 'K': (2, 5), 'Z': (0, 6)}) def test_define_class_diagram(self): diagram = self.build('define_class.diag') self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': (255, 255, 255), 'C': (255, 255, 255)}) self.assertNodeStyle(diagram, {'A': 'dashed', 'B': None, 'C': None}) self.assertEdgeColor(diagram, {('A', 'B'): (255, 0, 0), ('B', 'C'): (0, 0, 0)}) self.assertEdgeStyle(diagram, {('A', 'B'): 'dashed', ('B', 'C'): None}) ������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_builder_edge.py��������������������������������������������0000644�0000766�0000024�00000015215�12235371405�024141� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- from blockdiag.tests.utils import BuilderTestCase, capture_stderr class TestBuilderEdge(BuilderTestCase): def test_diagram_attributes(self): diagram = self.build('diagram_attributes.diag') self.assertEqual(2, len(diagram.nodes)) self.assertEqual(1, len(diagram.edges)) def test_single_edge_diagram(self): diagram = self.build('single_edge.diag') self.assertEqual(2, len(diagram.nodes)) self.assertEqual(1, len(diagram.edges)) self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0)}) self.assertNodeLabel(diagram, {'A': 'A', 'B': 'B'}) def test_two_edges_diagram(self): diagram = self.build('two_edges.diag') self.assertEqual(3, len(diagram.nodes)) self.assertEqual(2, len(diagram.edges)) self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0)}) def test_edge_shape(self): diagram = self.build('edge_shape.diag') self.assertEdgeDir(diagram, {('A', 'B'): 'none', ('B', 'C'): 'forward', ('C', 'D'): 'back', ('D', 'E'): 'both'}) def test_edge_attribute(self): diagram = self.build('edge_attribute.diag') self.assertEdgeDir(diagram, {('A', 'B'): 'forward', ('B', 'C'): 'forward', ('C', 'D'): 'forward', ('D', 'E'): 'none', ('E', 'F'): 'both', ('F', 'G'): 'forward'}) self.assertEdgeColor(diagram, {('A', 'B'): (255, 0, 0), # red ('B', 'C'): (255, 0, 0), # red ('C', 'D'): (255, 0, 0), # red ('D', 'E'): (0, 0, 0), ('E', 'F'): (255, 0, 0), # red ('F', 'G'): (0, 0, 0)}) self.assertEdgeThick(diagram, {('A', 'B'): None, ('B', 'C'): None, ('C', 'D'): None, ('D', 'E'): None, ('E', 'F'): None, ('F', 'G'): 3}) def test_folded_edge_diagram(self): diagram = self.build('folded_edge.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (0, 1), 'E': (0, 2), 'F': (1, 1), 'Z': (0, 3)}) def test_skipped_edge_right_diagram(self): diagram = self.build('skipped_edge_right.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): True, ('B', 'C'): False}) def test_skipped_edge_rightdown_diagram(self): diagram = self.build('skipped_edge_rightdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): True, ('A', 'D'): False, ('B', 'C'): False, ('B', 'D'): False}) def test_skipped_edge_up_diagram(self): diagram = self.build('skipped_edge_up.diag') self.assertEdgeSkipped(diagram, {('C', 'A'): True}) def test_skipped_edge_down_diagram(self): diagram = self.build('skipped_edge_down.diag') self.assertEdgeSkipped(diagram, {('A', 'C'): True}) def test_skipped_edge_leftdown_diagram(self): diagram = self.build('skipped_edge_leftdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('B', 'C'): False, ('B', 'D'): False, ('C', 'G'): True, ('F', 'G'): False}) @capture_stderr def test_skipped_edge_flowchart_rightdown_diagram(self): diagram = self.build('skipped_edge_flowchart_rightdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): False, ('A', 'D'): True, ('C', 'D'): False}) @capture_stderr def test_skipped_edge_flowchart_rightdown2_diagram(self): diagram = self.build('skipped_edge_flowchart_rightdown2.diag') self.assertEdgeSkipped(diagram, {('B', 'C'): False, ('A', 'C'): True}) def test_skipped_edge_portrait_right_diagram(self): diagram = self.build('skipped_edge_portrait_right.diag') self.assertEdgeSkipped(diagram, {('A', 'C'): True}) def test_skipped_edge_portrait_rightdown_diagram(self): diagram = self.build('skipped_edge_portrait_rightdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): False, ('A', 'E'): True, ('B', 'D'): False, ('C', 'E'): False}) def test_skipped_edge_portrait_leftdown_diagram(self): diagram = self.build('skipped_edge_portrait_leftdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('B', 'C'): False, ('D', 'C'): True, ('D', 'E'): False}) def test_skipped_edge_portrait_down_diagram(self): diagram = self.build('skipped_edge_portrait_down.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): True, ('B', 'C'): False}) @capture_stderr def test_skipped_edge_portrait_flowchart_rightdown_diagram(self): diagram = self.build('skipped_edge_portrait_flowchart_rightdown.diag') self.assertEdgeSkipped(diagram, {('A', 'B'): False, ('A', 'C'): False, ('A', 'D'): True, ('C', 'D'): False}) @capture_stderr def test_skipped_edge_portrait_flowchart_rightdown2_diagram(self): diagram = self.build('skipped_edge_portrait_flowchart_rightdown2.diag') self.assertEdgeSkipped(diagram, {('B', 'C'): False, ('A', 'C'): True}) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_builder_errors.py������������������������������������������0000644�0000766�0000024�00000007401�12251537642�024554� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- from blockdiag.tests.utils import BuilderTestCase from blockdiag.parser import ParseException class TestBuilderError(BuilderTestCase): def test_unknown_diagram_default_shape_diagram(self): filename = 'errors/unknown_diagram_default_shape.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_diagram_edge_layout_diagram(self): filename = 'errors/unknown_diagram_edge_layout.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_diagram_orientation_diagram(self): filename = 'errors/unknown_diagram_orientation.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_node_shape_diagram(self): filename = 'errors/unknown_node_shape.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_node_attribute_diagram(self): filename = 'errors/unknown_node_attribute.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_node_style_diagram(self): filename = 'errors/unknown_node_style.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_node_class_diagram(self): filename = 'errors/unknown_node_class.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_edge_dir_diagram(self): filename = 'errors/unknown_edge_dir.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_edge_style_diagram(self): filename = 'errors/unknown_edge_style.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_edge_hstyle_diagram(self): filename = 'errors/unknown_edge_hstyle.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_edge_class_diagram(self): filename = 'errors/unknown_edge_class.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_group_shape_diagram(self): filename = 'errors/unknown_group_shape.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_group_class_diagram(self): filename = 'errors/unknown_group_class.diag' with self.assertRaises(AttributeError): self.build(filename) def test_unknown_group_orientation_diagram(self): filename = 'errors/unknown_group_orientation.diag' with self.assertRaises(AttributeError): self.build(filename) def test_belongs_to_two_groups_diagram(self): filename = 'errors/belongs_to_two_groups.diag' with self.assertRaises(RuntimeError): self.build(filename) def test_unknown_plugin_diagram(self): filename = 'errors/unknown_plugin.diag' with self.assertRaises(AttributeError): self.build(filename) def test_node_follows_group_diagram(self): filename = 'errors/node_follows_group.diag' with self.assertRaises(ParseException): self.build(filename) def test_group_follows_node_diagram(self): filename = 'errors/group_follows_node.diag' with self.assertRaises(ParseException): self.build(filename) def test_unknown_diagram_type(self): filename = 'errors/unknown_diagram_type.diag' with self.assertRaises(ParseException): self.build(filename) def test_lexer_error_diagram(self): filename = 'errors/lexer_error.diag' with self.assertRaises(ParseException): self.build(filename) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_builder_group.py�������������������������������������������0000644�0000766�0000024�00000024031�12220301432�024350� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- from blockdiag.tests.utils import BuilderTestCase class TestBuilderGroup(BuilderTestCase): def test_nested_groups_diagram(self): diagram = self.build('nested_groups.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'Z': (0, 2)}) def test_nested_groups_and_edges_diagram(self): diagram = self.build('nested_groups_and_edges.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (2, 0), 'Z': (0, 1)}) def test_empty_group_diagram(self): diagram = self.build('empty_group.diag') self.assertNodeXY(diagram, {'Z': (0, 0)}) def test_empty_nested_group_diagram(self): diagram = self.build('empty_nested_group.diag') self.assertNodeXY(diagram, {'Z': (0, 0)}) def test_empty_group_declaration_diagram(self): diagram = self.build('empty_group_declaration.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'Z': (0, 1)}) def test_simple_group_diagram(self): diagram = self.build('simple_group.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}) def test_group_declare_as_node_attribute_diagram(self): diagram = self.build('group_declare_as_node_attribute.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'E': (2, 2), 'Z': (0, 3)}) def test_group_attribute(self): diagram = self.build('group_attribute.diag') groups = list(diagram.traverse_groups()) self.assertEqual(1, len(groups)) self.assertEqual((255, 0, 0), groups[0].color) self.assertEqual('line', groups[0].shape) def test_merge_groups_diagram(self): diagram = self.build('merge_groups.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (0, 1), 'D': (1, 1), 'Z': (0, 2)}) def test_node_attribute_and_group_diagram(self): diagram = self.build('node_attribute_and_group.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) self.assertNodeLabel(diagram, {'A': 'foo', 'B': 'bar', 'C': 'baz', 'Z': 'Z'}) self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': '#888888', 'C': (0, 0, 255), 'Z': (255, 255, 255)}) def test_group_sibling_diagram(self): diagram = self.build('group_sibling.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 2), 'D': (2, 0), 'E': (2, 1), 'F': (2, 2), 'Z': (0, 3)}) def test_group_order_diagram(self): diagram = self.build('group_order.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}) def test_group_order2_diagram(self): diagram = self.build('group_order2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (2, 1), 'E': (1, 2), 'F': (2, 2), 'Z': (0, 3)}) def test_group_order3_diagram(self): diagram = self.build('group_order3.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'E': (1, 2), 'Z': (0, 3)}) def test_group_children_height_diagram(self): diagram = self.build('group_children_height.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 0), 'F': (2, 2), 'Z': (0, 3)}) def test_group_children_order_diagram(self): diagram = self.build('group_children_order.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 0), 'F': (2, 1), 'G': (2, 2), 'Z': (0, 3)}) def test_group_children_order2_diagram(self): diagram = self.build('group_children_order2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 1), 'F': (2, 0), 'G': (2, 2), 'Z': (0, 3)}) def test_group_children_order3_diagram(self): diagram = self.build('group_children_order3.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 0), 'F': (2, 1), 'G': (2, 2), 'Q': (0, 3), 'Z': (0, 4)}) def test_group_children_order4_diagram(self): diagram = self.build('group_children_order4.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (2, 0), 'Z': (0, 3)}) def test_node_in_group_follows_outer_node_diagram(self): diagram = self.build('node_in_group_follows_outer_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_group_id_and_node_id_are_not_conflicted_diagram(self): diagram = self.build('group_id_and_node_id_are_not_conflicted.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (0, 1), 'D': (1, 1), 'Z': (0, 2)}) def test_outer_node_follows_node_in_group_diagram(self): diagram = self.build('outer_node_follows_node_in_group.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_large_group_and_node_diagram(self): diagram = self.build('large_group_and_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (1, 3), 'F': (2, 0), 'Z': (0, 4)}) def test_large_group_and_node2_diagram(self): diagram = self.build('large_group_and_node2.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (3, 0), 'E': (4, 0), 'F': (5, 0), 'Z': (0, 1)}) def test_large_group_and_two_nodes_diagram(self): diagram = self.build('large_group_and_two_nodes.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (1, 2), 'E': (1, 3), 'F': (2, 0), 'G': (2, 1), 'Z': (0, 4)}) def test_group_height_diagram(self): diagram = self.build('group_height.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'E': (1, 2), 'Z': (0, 3)}) def test_multiple_groups_diagram(self): diagram = self.build('multiple_groups.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (0, 2), 'D': (0, 3), 'E': (1, 0), 'F': (1, 1), 'G': (1, 2), 'H': (2, 0), 'I': (2, 1), 'J': (3, 0), 'Z': (0, 4)}) def test_multiple_nested_groups_diagram(self): diagram = self.build('multiple_nested_groups.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'Z': (0, 2)}) def test_group_works_node_decorator_diagram(self): diagram = self.build('group_works_node_decorator.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (3, 0), 'D': (2, 0), 'E': (1, 1), 'Z': (0, 2)}) def test_nested_groups_work_node_decorator_diagram(self): diagram = self.build('nested_groups_work_node_decorator.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'Z': (0, 2)}) def test_reversed_multiple_groups_diagram(self): diagram = self.build('reverse_multiple_groups.diag') self.assertNodeXY(diagram, {'A': (3, 0), 'B': (3, 1), 'C': (3, 2), 'D': (3, 3), 'E': (2, 0), 'F': (2, 1), 'G': (2, 2), 'H': (1, 0), 'I': (1, 1), 'J': (0, 0), 'Z': (0, 4)}) def test_group_and_skipped_edge_diagram(self): diagram = self.build('group_and_skipped_edge.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (3, 0), 'E': (1, 1), 'Z': (0, 2)}) def test_group_orientation_diagram(self): diagram = self.build('group_orientation.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (2, 1), 'Z': (0, 2)}) def test_nested_group_orientation_diagram(self): diagram = self.build('nested_group_orientation.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'C': (1, 0), 'Z': (0, 2)}) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_builder_node.py��������������������������������������������0000644�0000766�0000024�00000015374�12360215311�024160� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- from collections import defaultdict from blockdiag.tests.utils import BuilderTestCase class TestBuilderNode(BuilderTestCase): def test_single_node_diagram(self): diagram = self.build('single_node.diag') self.assertEqual(1, len(diagram.nodes)) self.assertEqual(0, len(diagram.edges)) self.assertEqual('A', diagram.nodes[0].label) self.assertEqual((0, 0), diagram.nodes[0].xy) def test_node_shape_diagram(self): expected = {'A': 'box', 'B': 'roundedbox', 'C': 'diamond', 'D': 'ellipse', 'E': 'note', 'F': 'cloud', 'G': 'mail', 'H': 'beginpoint', 'I': 'endpoint', 'J': 'minidiamond', 'K': 'flowchart.condition', 'L': 'flowchart.database', 'M': 'flowchart.input', 'N': 'flowchart.loopin', 'O': 'flowchart.loopout', 'P': 'actor', 'Q': 'flowchart.terminator', 'R': 'textbox', 'S': 'dots', 'T': 'none', 'U': 'square', 'V': 'circle', 'Z': 'box'} diagram = self.build('node_shape.diag') self.assertNodeShape(diagram, expected) def test_node_shape_namespace_diagram(self): diagram = self.build('node_shape_namespace.diag') self.assertNodeShape(diagram, {'A': 'flowchart.condition', 'B': 'condition', 'Z': 'box'}) def test_node_has_multilined_label_diagram(self): diagram = self.build('node_has_multilined_label.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'Z': (0, 1)}) self.assertNodeLabel(diagram, {'A': "foo\nbar", 'Z': 'Z'}) def test_quoted_node_id_diagram(self): diagram = self.build('quoted_node_id.diag') self.assertNodeXY(diagram, {'A': (0, 0), "'A'": (1, 0), 'B': (2, 0), 'Z': (0, 1)}) def test_node_id_includes_dot_diagram(self): diagram = self.build('node_id_includes_dot.diag') self.assertNodeXY(diagram, {'A.B': (0, 0), 'C.D': (1, 0), 'Z': (0, 1)}) def test_multiple_nodes_definition_diagram(self): diagram = self.build('multiple_nodes_definition.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (0, 1), 'Z': (0, 2)}) self.assertNodeColor(diagram, {'A': (255, 0, 0), 'B': (255, 0, 0), 'Z': (255, 255, 255)}) def test_multiple_node_relation_diagram(self): diagram = self.build('multiple_node_relation.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (1, 1), 'D': (2, 0), 'Z': (0, 2)}) def test_node_attribute(self): labels = {'A': 'B', 'B': 'double quoted', 'C': 'single quoted', 'D': '\'"double" quoted\'', 'E': '"\'single\' quoted"', 'F': 'F', 'G': 'G', 'H': 'H', 'I': 'I', 'J': 'Hello', 'K': 'K'} colors = {'A': (255, 0, 0), 'B': (255, 255, 255), 'C': (255, 0, 0), 'D': (255, 0, 0), 'E': (255, 0, 0), 'F': (255, 255, 255), 'G': (255, 255, 255), 'H': (255, 255, 255), 'I': (255, 255, 255), 'J': (255, 255, 255), 'K': (255, 255, 255)} textcolors = defaultdict(lambda: (0, 0, 0)) textcolors['F'] = (255, 0, 0) linecolors = defaultdict(lambda: (0, 0, 0)) linecolors['I'] = (255, 0, 0) numbered = defaultdict(lambda: None) numbered['E'] = '1' stacked = defaultdict(lambda: False) stacked['G'] = True fontsize = defaultdict(lambda: None) fontsize['H'] = 16 orientations = defaultdict(lambda: 'horizontal') orientations['J'] = 'vertical' backgrounds = defaultdict(lambda: None) backgrounds['K'] = ('src/blockdiag/tests/diagrams/' 'debian-logo-256color-palettealpha.png') diagram = self.build('node_attribute.diag') self.assertNodeLabel(diagram, labels) self.assertNodeColor(diagram, colors) self.assertNodeTextColor(diagram, textcolors) self.assertNodeLineColor(diagram, linecolors) self.assertNodeNumbered(diagram, numbered) self.assertNodeStacked(diagram, stacked) self.assertNodeFontsize(diagram, fontsize) self.assertNodeLabel_Orientation(diagram, orientations) self.assertNodeBackground(diagram, backgrounds) def test_node_height_diagram(self): diagram = self.build('node_height.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (2, 1), 'E': (1, 1), 'Z': (0, 2)}) def test_branched_diagram(self): diagram = self.build('branched.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'D': (1, 1), 'E': (2, 1), 'Z': (0, 2)}) def test_multiple_parent_node_diagram(self): diagram = self.build('multiple_parent_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (0, 2), 'D': (1, 2), 'E': (0, 1), 'Z': (0, 3)}) def test_twin_multiple_parent_node_diagram(self): diagram = self.build('twin_multiple_parent_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (0, 1), 'D': (1, 1), 'E': (0, 2), 'Z': (0, 3)}) def test_flowable_node_diagram(self): diagram = self.build('flowable_node.diag') self.assertNodeXY(diagram, {'A': (0, 0), 'B': (1, 0), 'C': (2, 0), 'Z': (0, 1)}) def test_plugin_autoclass_diagram(self): diagram = self.build('plugin_autoclass.diag') self.assertNodeXY(diagram, {'A_emphasis': (0, 0), 'B_emphasis': (1, 0), 'C': (1, 1)}) self.assertNodeStyle(diagram, {'A_emphasis': 'dashed', 'B_emphasis': 'dashed', 'C': None}) self.assertNodeColor(diagram, {'A_emphasis': (255, 0, 0), 'B_emphasis': (255, 0, 0), 'C': (255, 255, 255)}) def test_plugin_attributes_diagram(self): diagram = self.build('plugin_attributes.diag') self.assertNodeTest_Attr1(diagram, {'A': "1", 'B': None}) self.assertNodeTest_Attr2(diagram, {'A': "2", 'B': None}) self.assertNodeTest_Attr3(diagram, {'A': "3", 'B': None}) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_builder_separate.py����������������������������������������0000644�0000766�0000024�00000003127�12220762742�025042� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- from __future__ import print_function from blockdiag.builder import SeparateDiagramBuilder from blockdiag.elements import DiagramNode from blockdiag.tests.utils import BuilderTestCase class TestBuilderSeparated(BuilderTestCase): def _build(self, tree): return SeparateDiagramBuilder.build(tree) def test_separate1_diagram(self): diagram = self.build('separate1.diag') assert_pos = {0: {'B': (0, 0), 'C': (1, 0), 'D': (4, 0), 'E': (2, 0), 'F': (3, 0)}, 1: {'A': (0, 0), 'B': (1, 0), 'D': (3, 0)}, 2: {'A': (0, 0), 'Z': (0, 1)}} for i, diagram in enumerate(diagram): for node in diagram.traverse_nodes(): if isinstance(node, DiagramNode): print(node) self.assertEqual(assert_pos[i][node.id], node.xy) def test_separate2_diagram(self): diagram = self.build('separate2.diag') assert_pos = {0: {'A': (0, 0), 'C': (1, 0), 'D': (2, 0), 'E': (0, 2), 'G': (3, 0), 'H': (3, 1)}, 1: {'A': (0, 0), 'B': (1, 0), 'E': (2, 0), 'F': (4, 2), 'G': (4, 0), 'H': (4, 1)}, 2: {'A': (0, 0), 'F': (2, 2), 'G': (2, 0), 'H': (2, 1), 'Z': (0, 3)}} for i, diagram in enumerate(diagram): for node in diagram.traverse_nodes(): if isinstance(node, DiagramNode): print(node) self.assertEqual(assert_pos[i][node.id], node.xy) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_command.py�������������������������������������������������0000644�0000766�0000024�00000004153�12472051306�023142� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- import os import sys from blockdiag.command import BlockdiagApp from blockdiag.tests.utils import TemporaryDirectory if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class TestBlockdiagApp(unittest.TestCase): def test_app_cleans_up_images(self): testdir = os.path.dirname(__file__) diagpath = os.path.join(testdir, 'diagrams', 'background_url_image.diag') urlopen_cache = {} def cleanup(): from blockdiag.utils import images urlopen_cache.update(images.urlopen_cache) try: tmpdir = TemporaryDirectory() fd, tmpfile = tmpdir.mkstemp() os.close(fd) args = ['-T', 'SVG', '-o', tmpfile, diagpath] app = BlockdiagApp() app.register_cleanup_handler(cleanup) # to get internal state app.run(args) self.assertTrue(urlopen_cache) # check images were cached for path in urlopen_cache.values(): self.assertFalse(os.path.exists(path)) # and removed finally finally: tmpdir.clean() def test_app_cleans_up_plugins(self): testdir = os.path.dirname(__file__) diagpath = os.path.join(testdir, 'diagrams', 'plugin_autoclass.diag') loaded_plugins = [] def cleanup(): from blockdiag import plugins loaded_plugins.extend(plugins.loaded_plugins) try: tmpdir = TemporaryDirectory() fd, tmpfile = tmpdir.mkstemp() os.close(fd) args = ['-T', 'SVG', '-o', tmpfile, diagpath] app = BlockdiagApp() app.register_cleanup_handler(cleanup) # to get internal state app.run(args) from blockdiag import plugins self.assertTrue(loaded_plugins) # check plugins were loaded self.assertFalse(plugins.loaded_plugins) # and unloaded finally finally: tmpdir.clean() ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_generate_diagram.py����������������������������������������0000644�0000766�0000024�00000012002�12472051240�024767� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- import os import re import sys from nose.tools import nottest import blockdiag import blockdiag.command from blockdiag.tests.utils import capture_stderr, TemporaryDirectory from blockdiag.tests.utils import supported_pil, supported_pdf if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest TESTDIR = os.path.dirname(__file__) FONTPATH = os.path.join(TESTDIR, 'VLGothic', 'VL-Gothic-Regular.ttf') def get_fontpath(testdir): return os.path.join(testdir, 'VLGothic', 'VL-Gothic-Regular.ttf') def get_diagram_files(testdir): diagramsdir = os.path.join(testdir, 'diagrams') skipped = ['README', 'debian-logo-256color-palettealpha.png', 'errors', 'white.gif'] for file in os.listdir(diagramsdir): if file in skipped: pass else: yield os.path.join(diagramsdir, file) def test_generate(): mainfunc = blockdiag.command.main basepath = os.path.dirname(__file__) files = get_diagram_files(basepath) options = [] for testcase in testcase_generator(basepath, mainfunc, files, options): yield testcase def test_generate_with_separate(): mainfunc = blockdiag.command.main basepath = os.path.dirname(__file__) files = get_diagram_files(basepath) filtered = (f for f in files if re.search('separate', f)) options = ['--separate'] for testcase in testcase_generator(basepath, mainfunc, filtered, options): yield testcase @nottest def testcase_generator(basepath, mainfunc, files, options): fontpath = get_fontpath(basepath) options = options + ['-f', fontpath] for source in files: yield generate, mainfunc, 'svg', source, options if not supported_pil(): yield unittest.skip("Pillow is not available")(generate) yield unittest.skip("Pillow is not available")(generate) elif os.environ.get('ALL_TESTS') is None: message = "Skipped by default. To enable it, specify $ALL_TESTS=1" yield unittest.skip(message)(generate) yield unittest.skip(message)(generate) else: yield generate, mainfunc, 'png', source, options yield generate, mainfunc, 'png', source, options + ['--antialias'] if not supported_pdf(): yield unittest.skip("reportlab is not available")(generate) elif os.environ.get('ALL_TESTS') is None: message = "Skipped by default. To enable it, specify $ALL_TESTS=1" yield unittest.skip(message)(generate) else: yield generate, mainfunc, 'pdf', source, options @capture_stderr def generate(mainfunc, filetype, source, options): try: tmpdir = TemporaryDirectory() fd, tmpfile = tmpdir.mkstemp() os.close(fd) mainfunc(['--debug', '-T', filetype, '-o', tmpfile, source] + list(options)) finally: tmpdir.clean() def not_exist_font_config_option_test(): args = ['-f', '/font_is_not_exist', '-f', FONTPATH, 'input.diag'] options = blockdiag.command.BlockdiagOptions(blockdiag).parse(args) from blockdiag.utils.bootstrap import detectfont detectfont(options) def stdin_test(): testdir = os.path.dirname(__file__) diagpath = os.path.join(testdir, 'diagrams', 'single_edge.diag') try: stdin = sys.stdin sys.stdin = open(diagpath, 'r') tmpdir = TemporaryDirectory() fd, tmpfile = tmpdir.mkstemp() os.close(fd) args = ['-T', 'SVG', '-o', tmpfile, '-'] ret = blockdiag.command.main(args) assert ret == 0 finally: sys.stdin = stdin tmpdir.clean() @capture_stderr def ghostscript_not_found_test(): testdir = os.path.dirname(__file__) diagpath = os.path.join(testdir, 'diagrams', 'background_url_image.diag') try: old_path = os.environ['PATH'] os.environ['PATH'] = '' tmpdir = TemporaryDirectory() fd, tmpfile = tmpdir.mkstemp() os.close(fd) args = ['-T', 'SVG', '-o', tmpfile, diagpath] ret = blockdiag.command.main(args) assert 'Could not convert image:' in sys.stderr.getvalue() assert ret == 0 finally: tmpdir.clean() os.environ['PATH'] = old_path @capture_stderr def svg_includes_source_code_tag_test(): from xml.etree import ElementTree testdir = os.path.dirname(__file__) diagpath = os.path.join(testdir, 'diagrams', 'single_edge.diag') try: tmpdir = TemporaryDirectory() fd, tmpfile = tmpdir.mkstemp() os.close(fd) args = ['-T', 'SVG', '-o', tmpfile, diagpath] blockdiag.command.main(args) # compare embeded source code source_code = open(diagpath).read() tree = ElementTree.parse(tmpfile) desc = tree.find('{http://www.w3.org/2000/svg}desc') # strip spaces source_code = re.sub('\s+', ' ', source_code) embeded = re.sub('\s+', ' ', desc.text) assert source_code == embeded finally: tmpdir.clean() ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_imagedraw_textfolder.py������������������������������������0000644�0000766�0000024�00000006061�12472051345�025727� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- import sys from blockdiag.imagedraw.textfolder import splitlabel from blockdiag.imagedraw.textfolder import splittext from blockdiag.imagedraw.textfolder import truncate_text from blockdiag.utils import Size from blockdiag.utils.compat import u if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest CHAR_WIDTH = 14 CHAR_HEIGHT = 10 class Metrics(object): def textsize(self, text): length = len(text) return Size(CHAR_WIDTH * length, CHAR_HEIGHT) class TestTextFolder(unittest.TestCase): def test_splitlabel(self): # single line text text = "abc" self.assertEqual(['abc'], list(splitlabel(text))) # text include \n (as char a.k.a. \x5c) text = "abc\ndef" self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text include \n (as mac yensign a.k.a. \xa5) text = "abc\xa5ndef" self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text includes \n (as text) text = "abc\\ndef" self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text includes escaped \n text = "abc\\\\ndef" self.assertEqual(['abc\\ndef'], list(splitlabel(text))) # text includes escaped \n (\x5c and mac yensign mixed) if sys.version_info[0] == 2: text = u("abc\xa5\\\\ndef") else: text = u("abc\xa5\\ndef") self.assertEqual(['abc\\ndef'], list(splitlabel(text))) # text include \n and spaces text = " abc \n def " self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text starts empty line text = " \nabc\ndef" self.assertEqual(['abc', 'def'], list(splitlabel(text))) # text starts empty line with \n (as text) text = " \\nabc\\ndef" self.assertEqual(['', 'abc', 'def'], list(splitlabel(text))) def test_splittext_width(self): metrics = Metrics() # just fit text text = "abc" ret = splittext(metrics, text, CHAR_WIDTH * 3) self.assertEqual(['abc'], ret) # text should be folded (once) text = "abcdef" ret = splittext(metrics, text, CHAR_WIDTH * 3) self.assertEqual(['abc', 'def'], ret) # text should be folded (twice) text = "abcdefghi" ret = splittext(metrics, text, CHAR_WIDTH * 3) self.assertEqual(['abc', 'def', 'ghi'], ret) # empty text text = "" ret = splittext(metrics, text, CHAR_WIDTH * 3) self.assertEqual([' '], ret) def test_truncate_text(self): metrics = Metrics() # truncated text = "abcdef" ret = truncate_text(metrics, text, CHAR_WIDTH * 8) self.assertEqual("abcd ...", ret) # truncated text = "abcdef" ret = truncate_text(metrics, text, CHAR_WIDTH * 5) self.assertEqual("a ...", ret) # not truncated (too short) text = "abcdef" ret = truncate_text(metrics, text, CHAR_WIDTH * 4) self.assertEqual("abcdef", ret) �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_imagedraw_utils.py�����������������������������������������0000644�0000766�0000024�00000003601�12472051270�024701� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- import sys from blockdiag.imagedraw.utils import ( is_zenkaku, zenkaku_len, hankaku_len, string_width, textsize ) from blockdiag.utils.compat import u if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class TestUtils(unittest.TestCase): def test_is_zenkaku(self): # A self.assertEqual(False, is_zenkaku(u("A"))) # あ self.assertEqual(True, is_zenkaku(u("\u3042"))) def test_zenkaku_len(self): # abc self.assertEqual(0, zenkaku_len(u("abc"))) # あいう self.assertEqual(3, zenkaku_len(u("\u3042\u3044\u3046"))) # あいc self.assertEqual(2, zenkaku_len(u("\u3042\u3044c"))) def test_hankaku_len(self): # abc self.assertEqual(3, hankaku_len(u("abc"))) # あいう self.assertEqual(0, hankaku_len(u("\u3042\u3044\u3046"))) # あいc self.assertEqual(1, hankaku_len(u("\u3042\u3044c"))) def test_string_width(self): # abc self.assertEqual(3, string_width(u("abc"))) # あいう self.assertEqual(6, string_width(u("\u3042\u3044\u3046"))) # あいc self.assertEqual(5, string_width(u("\u3042\u3044c"))) def test_test_textsize(self): from blockdiag.utils.fontmap import FontInfo font = FontInfo('serif', None, 11) # abc self.assertEqual((19, 11), textsize(u("abc"), font)) # あいう self.assertEqual((33, 11), textsize(u("\u3042\u3044\u3046"), font)) # あいc self.assertEqual((29, 11), textsize(u("\u3042\u3044c"), font)) # abc font = FontInfo('serif', None, 24) self.assertEqual((40, 24), textsize(u("abc"), font)) # あいう font = FontInfo('serif', None, 18) self.assertEqual((54, 18), textsize(u("\u3042\u3044\u3046"), font)) �������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_parser.py��������������������������������������������������0000644�0000766�0000024�00000011135�12472051163�023017� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- from __future__ import print_function import sys from blockdiag.parser import parse_string, ParseException from blockdiag.parser import Diagram, Group, Statements, Node, Edge if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class TestParser(unittest.TestCase): def test_basic(self): # basic digram code = """ diagram test { A -> B -> C, D; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) def test_without_diagram_id(self): code = """ diagram { A -> B -> C, D; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) code = """ { A -> B -> C, D; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) def test_empty_diagram(self): code = """ diagram { } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) code = """ { } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) def test_diagram_includes_nodes(self): code = """ diagram { A; B [label = "foobar"]; C [color = "red"]; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(3, len(tree.stmts)) self.assertIsInstance(tree.stmts[0], Statements) self.assertIsInstance(tree.stmts[0].stmts[0], Node) self.assertIsInstance(tree.stmts[1], Statements) self.assertIsInstance(tree.stmts[1].stmts[0], Node) self.assertIsInstance(tree.stmts[2], Statements) self.assertIsInstance(tree.stmts[2].stmts[0], Node) def test_diagram_includes_edges(self): code = """ diagram { A -> B -> C; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(1, len(tree.stmts)) self.assertIsInstance(tree.stmts[0], Statements) self.assertEqual(2, len(tree.stmts[0].stmts)) self.assertIsInstance(tree.stmts[0].stmts[0], Edge) self.assertIsInstance(tree.stmts[0].stmts[1], Edge) code = """ diagram { A -> B -> C [style = dotted]; D -> E, F; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(2, len(tree.stmts)) self.assertIsInstance(tree.stmts[0], Statements) self.assertEqual(2, len(tree.stmts[0].stmts)) self.assertIsInstance(tree.stmts[0].stmts[0], Edge) self.assertIsInstance(tree.stmts[0].stmts[1], Edge) self.assertIsInstance(tree.stmts[1], Statements) self.assertEqual(1, len(tree.stmts[1].stmts)) self.assertIsInstance(tree.stmts[1].stmts[0], Edge) def test_diagram_includes_groups(self): code = """ diagram { group { A; B; } group { C -> D; } } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(2, len(tree.stmts)) self.assertIsInstance(tree.stmts[0], Group) self.assertEqual(2, len(tree.stmts[0].stmts)) self.assertIsInstance(tree.stmts[0].stmts[0], Statements) self.assertIsInstance(tree.stmts[0].stmts[0].stmts[0], Node) self.assertIsInstance(tree.stmts[0].stmts[1], Statements) self.assertIsInstance(tree.stmts[0].stmts[1].stmts[0], Node) self.assertIsInstance(tree.stmts[1], Group) self.assertEqual(1, len(tree.stmts[1].stmts)) self.assertIsInstance(tree.stmts[1].stmts[0], Statements) self.assertIsInstance(tree.stmts[1].stmts[0].stmts[0], Edge) def test_diagram_includes_diagram_attributes(self): code = """ diagram { fontsize = 12; node_width = 80; } """ tree = parse_string(code) self.assertIsInstance(tree, Diagram) self.assertEqual(2, len(tree.stmts)) def test_parenthesis_ness(self): with self.assertRaises(ParseException): code = "" parse_string(code) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_utils.py���������������������������������������������������0000644�0000766�0000024�00000002442�12472051214�022661� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- import sys from blockdiag.utils import Size, unquote if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest class TestUtils(unittest.TestCase): def test_size_resize(self): size = Size(10, 20) resized = size.resize(width=50, height=50) self.assertEqual((50, 50), resized) resized = size.resize(width=50) self.assertEqual((50, 100), resized) resized = size.resize(height=50) self.assertEqual((25, 50), resized) resized = size.resize(scale=50) self.assertEqual((5, 10), resized) resized = size.resize(width=50, scale=50) self.assertEqual((25, 50), resized) resized = size.resize(height=50, scale=50) self.assertEqual((12.5, 25), resized) resized = size.resize(width=50, height=50, scale=50) self.assertEqual((25, 25), resized) def test_size_to_integer_point(self): size = Size(1.5, 2.5) self.assertEqual((1, 2), size.to_integer_point()) def test_unquote(self): self.assertEqual('test', unquote('"test"')) self.assertEqual('test', unquote("'test'")) self.assertEqual("'half quoted", unquote("'half quoted")) self.assertEqual('"half quoted', unquote('"half quoted')) ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/test_utils_fontmap.py�������������������������������������������0000644�0000766�0000024�00000031533�12472051044�024411� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- import os import sys import tempfile from io import StringIO from collections import namedtuple from blockdiag.utils.compat import u from blockdiag.utils.fontmap import FontInfo, FontMap from blockdiag.tests.utils import capture_stderr if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest FontElement = namedtuple('FontElement', 'fontfamily fontsize') class TestUtilsFontmap(unittest.TestCase): def setUp(self): fontpath1 = __file__ fontpath2 = os.path.join(os.path.dirname(__file__), 'utils.py') self.fontpath = [fontpath1, fontpath2] def test_fontinfo_new(self): FontInfo("serif", None, 11) FontInfo("sansserif", None, 11) FontInfo("monospace", None, 11) FontInfo("cursive", None, 11) FontInfo("fantasy", None, 11) FontInfo("serif-bold", None, 11) FontInfo("sansserif-italic", None, 11) FontInfo("monospace-oblique", None, 11) FontInfo("my-cursive", None, 11) FontInfo("-fantasy", None, 11) def test_fontinfo_invalid_familyname1(self): with self.assertRaises(AttributeError): FontInfo("unknown", None, 11) def test_fontinfo_invalid_familyname2(self): with self.assertRaises(AttributeError): FontInfo("sansserif-", None, 11) def test_fontinfo_invalid_familyname3(self): with self.assertRaises(AttributeError): FontInfo("monospace-unkown", None, 11) def test_fontinfo_invalid_familyname4(self): with self.assertRaises(AttributeError): FontInfo("cursive-bold-bold", None, 11) def test_fontinfo_invalid_familyname5(self): with self.assertRaises(AttributeError): FontInfo("SERIF", None, 11) def test_fontinfo_invalid_fontsize1(self): with self.assertRaises(TypeError): FontInfo("serif", None, None) def test_fontinfo_invalid_fontsize2(self): with self.assertRaises(ValueError): FontInfo("serif", None, '') def test_fontinfo_parse(self): font = FontInfo("serif", None, 11) self.assertEqual('', font.name) self.assertEqual('serif', font.generic_family) self.assertEqual('normal', font.weight) self.assertEqual('normal', font.style) font = FontInfo("sansserif-bold", None, 11) self.assertEqual('', font.name) self.assertEqual('sans-serif', font.generic_family) self.assertEqual('bold', font.weight) self.assertEqual('normal', font.style) font = FontInfo("monospace-italic", None, 11) self.assertEqual('', font.name) self.assertEqual('monospace', font.generic_family) self.assertEqual('normal', font.weight) self.assertEqual('italic', font.style) font = FontInfo("my-cursive-oblique", None, 11) self.assertEqual('my', font.name) self.assertEqual('cursive', font.generic_family) self.assertEqual('normal', font.weight) self.assertEqual('oblique', font.style) font = FontInfo("my-fantasy-bold", None, 11) self.assertEqual('my', font.name) self.assertEqual('fantasy', font.generic_family) self.assertEqual('bold', font.weight) self.assertEqual('normal', font.style) font = FontInfo("serif-serif", None, 11) self.assertEqual('serif', font.name) self.assertEqual('serif', font.generic_family) self.assertEqual('normal', font.weight) self.assertEqual('normal', font.style) def test_fontinfo_familyname(self): font = FontInfo("serif", None, 11) self.assertEqual('serif-normal', font.familyname) font = FontInfo("sansserif-bold", None, 11) self.assertEqual('sansserif-bold', font.familyname) font = FontInfo("monospace-italic", None, 11) self.assertEqual('monospace-italic', font.familyname) font = FontInfo("my-cursive-oblique", None, 11) self.assertEqual('my-cursive-oblique', font.familyname) font = FontInfo("my-fantasy-bold", None, 11) self.assertEqual('my-fantasy-bold', font.familyname) font = FontInfo("serif-serif", None, 11) self.assertEqual('serif-serif-normal', font.familyname) font = FontInfo("-serif", None, 11) self.assertEqual('serif-normal', font.familyname) @capture_stderr def test_fontmap_empty_config(self): config = StringIO(u("")) fmap = FontMap(config) font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sans-serif', font1.generic_family) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) element = FontElement('sansserif', 11) font2 = fmap.find(element) self.assertEqual(font1.familyname, font2.familyname) self.assertEqual(font1.path, font2.path) self.assertEqual(font1.size, font2.size) element = FontElement('sansserif-normal', 11) font3 = fmap.find(element) self.assertEqual(font1.familyname, font3.familyname) self.assertEqual(font1.path, font3.path) self.assertEqual(font1.size, font3.size) # non-registered familyname element = FontElement('my-sansserif-normal', 11) font4 = fmap.find(element) self.assertEqual(font1.familyname, font4.familyname) self.assertEqual(font1.path, font4.path) self.assertEqual(font1.size, font4.size) @capture_stderr def test_fontmap_none_config(self): fmap = FontMap() font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sans-serif', font1.generic_family) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) def test_fontmap_normal_config(self): _config = u("[fontmap]\nsansserif: %s\nsansserif-bold: %s\n") % \ (self.fontpath[0], self.fontpath[1]) config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sans-serif', font1.generic_family) self.assertEqual(self.fontpath[0], font1.path) self.assertEqual(11, font1.size) element = FontElement('sansserif', 11) font2 = fmap.find(element) self.assertEqual(font1.familyname, font2.familyname) self.assertEqual(font1.path, font2.path) self.assertEqual(font1.size, font2.size) element = FontElement('sansserif-normal', 11) font3 = fmap.find(element) self.assertEqual(font1.familyname, font3.familyname) self.assertEqual(font1.path, font3.path) self.assertEqual(font1.size, font3.size) element = FontElement('sansserif-bold', 11) font4 = fmap.find(element) self.assertEqual('sansserif-bold', font4.familyname) self.assertEqual(self.fontpath[1], font4.path) self.assertEqual(font1.size, font4.size) element = FontElement(None, None) font5 = fmap.find(element) self.assertEqual(font1.familyname, font5.familyname) self.assertEqual(font1.path, font5.path) self.assertEqual(font1.size, font5.size) element = object() font6 = fmap.find(element) self.assertEqual(font1.familyname, font6.familyname) self.assertEqual(font1.path, font6.path) self.assertEqual(font1.size, font6.size) def test_fontmap_duplicated_fontentry1(self): _config = u("[fontmap]\nsansserif: %s\nsansserif: %s\n") % \ (self.fontpath[0], self.fontpath[1]) config = StringIO(_config) if sys.version_info[0] == 2: fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sans-serif', font1.generic_family) self.assertEqual(self.fontpath[1], font1.path) self.assertEqual(11, font1.size) else: import configparser with self.assertRaises(configparser.DuplicateOptionError): FontMap(config) def test_fontmap_duplicated_fontentry2(self): # this testcase is only for python2.6 or later if sys.version_info > (2, 6): _config = u("[fontmap]\nsansserif: %s\nsansserif-normal: %s\n") % \ (self.fontpath[0], self.fontpath[1]) config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sans-serif', font1.generic_family) self.assertEqual(self.fontpath[1], font1.path) self.assertEqual(11, font1.size) def test_fontmap_with_capital_character(self): _config = u("[fontmap]\nCapitalCase-sansserif: %s\n") % \ self.fontpath[0] config = StringIO(_config) fmap = FontMap(config) element = FontElement('CapitalCase-sansserif', 11) font1 = fmap.find(element) self.assertEqual('sans-serif', font1.generic_family) self.assertEqual('capitalcase-sansserif-normal', font1.familyname) self.assertEqual(self.fontpath[0], font1.path) self.assertEqual(11, font1.size) @capture_stderr def test_fontmap_with_nodefault_fontentry(self): _config = u("[fontmap]\nserif: %s\n") % self.fontpath[0] config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sans-serif', font1.generic_family) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) element = FontElement('serif', 11) font2 = fmap.find(element) self.assertEqual('serif', font2.generic_family) self.assertEqual(self.fontpath[0], font2.path) self.assertEqual(font1.size, font2.size) element = FontElement('fantasy', 20) font3 = fmap.find(element) self.assertEqual('sans-serif', font3.generic_family) self.assertEqual(None, font3.path) self.assertEqual(20, font3.size) @capture_stderr def test_fontmap_with_nonexistence_fontpath(self): _config = u("[fontmap]\nserif: unknown_file\n") config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sans-serif', font1.generic_family) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) def test_fontmap_switch_defaultfamily(self): _config = u("[fontmap]\nserif-bold: %s\n") % self.fontpath[0] config = StringIO(_config) fmap = FontMap(config) font1 = fmap.find() self.assertEqual('sansserif-normal', font1.familyname) self.assertEqual(None, font1.path) self.assertEqual(11, font1.size) fmap.set_default_fontfamily('serif-bold') font2 = fmap.find() self.assertEqual('serif-bold', font2.familyname) self.assertEqual(self.fontpath[0], font2.path) self.assertEqual(11, font2.size) fmap.set_default_fontfamily('fantasy-italic') font3 = fmap.find() self.assertEqual('fantasy-italic', font3.familyname) self.assertEqual(None, font3.path) self.assertEqual(11, font3.size) fmap.fontsize = 20 font4 = fmap.find() self.assertEqual('fantasy-italic', font4.familyname) self.assertEqual(None, font4.path) self.assertEqual(20, font4.size) def test_fontmap_using_fontalias(self): _config = (u("[fontmap]\nserif-bold: %s\n") + u("[fontalias]\ntest = serif-bold\n")) % self.fontpath[0] config = StringIO(_config) fmap = FontMap(config) element = FontElement('test', 20) font1 = fmap.find(element) self.assertEqual('serif-bold', font1.familyname) self.assertEqual(self.fontpath[0], font1.path) self.assertEqual(20, font1.size) def test_fontmap_by_file(self): tmp = tempfile.mkstemp() _config = u("[fontmap]\nsansserif: %s\nsansserif-bold: %s\n") % \ (self.fontpath[0], self.fontpath[1]) fp = os.fdopen(tmp[0], 'wt') fp.write(_config) fp.close() fmap = FontMap(tmp[1]) font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sans-serif', font1.generic_family) self.assertEqual(self.fontpath[0], font1.path) self.assertEqual(11, font1.size) os.unlink(tmp[1]) def test_fontmap_including_bom_by_file(self): tmp = tempfile.mkstemp() _config = (u("[fontmap]\nsansserif: %s\n") + u("sansserif-bold: %s\n")) % \ (self.fontpath[0], self.fontpath[1]) try: fp = os.fdopen(tmp[0], 'wb') fp.write(_config.encode('utf-8-sig')) fp.close() fmap = FontMap(tmp[1]) font1 = fmap.find() self.assertTrue(font1) self.assertEqual('sans-serif', font1.generic_family) self.assertEqual(self.fontpath[0], font1.path) self.assertEqual(11, font1.size) finally: os.unlink(tmp[1]) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/tests/utils.py��������������������������������������������������������0000644�0000766�0000024�00000006371�12472051147�021634� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- from __future__ import print_function import os import re import sys import functools from shutil import rmtree from tempfile import mkdtemp, mkstemp from blockdiag.builder import ScreenNodeBuilder from blockdiag.parser import parse_file if sys.version_info < (2, 7): import unittest2 as unittest else: import unittest try: # sys.stderr in py2.x allows mixture of str and unicode from cStringIO import StringIO except ImportError: # sys.stderr in py3.x allows only str objects (disallow bytes objs) from io import StringIO def supported_pil(): try: from PIL import _imagingft _imagingft return True except: return False def with_pil(fn): if not supported_pil(): fn.__test__ = False return fn def supported_pdf(): try: import reportlab reportlab return True except: return False def with_pdf(fn): if not supported_pdf(): fn.__test__ = False return fn def capture_stderr(func): def wrap(*args, **kwargs): try: stderr = sys.stderr sys.stderr = StringIO() func(*args, **kwargs) if re.search('(ERROR|Traceback)', sys.stderr.getvalue()): raise AssertionError('Caught error') finally: if sys.stderr.getvalue(): print("---[ stderr ] ---") print(sys.stderr.getvalue()) sys.stderr = stderr return functools.wraps(func)(wrap) stderr_wrapper = capture_stderr # FIXME: deprecated class TemporaryDirectory(object): def __init__(self, suffix='', prefix='tmp', dir=None): self.name = mkdtemp(suffix, prefix, dir) def __del__(self): self.clean() def clean(self): if os.path.exists(self.name): rmtree(self.name) def mkstemp(self, suffix='', prefix='tmp', text=False): return mkstemp(suffix, prefix, self.name, text) class BuilderTestCase(unittest.TestCase): def build(self, filename): basedir = os.path.dirname(__file__) pathname = os.path.join(basedir, 'diagrams', filename) return self._build(parse_file(pathname)) def _build(self, tree): return ScreenNodeBuilder.build(tree) def __getattr__(self, name): if name.startswith('assertNode'): def asserter(diagram, attributes): attr_name = name.replace('assertNode', '').lower() print("[node.%s]" % attr_name) for node in (n for n in diagram.nodes if n.drawable): print(node) excepted = attributes[node.id] self.assertEqual(excepted, getattr(node, attr_name)) return asserter elif name.startswith('assertEdge'): def asserter(diagram, attributes): attr_name = name.replace('assertEdge', '').lower() print("[edge.%s]" % attr_name) for edge in diagram.edges: print(edge) expected = attributes[(edge.node1.id, edge.node2.id)] self.assertEqual(expected, getattr(edge, attr_name)) return asserter else: return getattr(super(BuilderTestCase, self), name) �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/����������������������������������������������������������������0000755�0000766�0000024�00000000000�12556430040�020105� 5����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/__init__.py�����������������������������������������������������0000644�0000766�0000024�00000011622�12427536055�022232� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import re import math class XY(tuple): mapper = dict(x=0, y=1) def __new__(cls, x, y): return super(XY, cls).__new__(cls, (x, y)) def __getattr__(self, name): try: return self[self.mapper[name]] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): raise TypeError("'XY' object does not support item assignment") def shift(self, x=0, y=0): return self.__class__(self.x + x, self.y + y) class Size(tuple): def __new__(cls, width, height): return super(Size, cls).__new__(cls, (width, height)) @property def width(self): return self[0] @property def height(self): return self[1] def resize(self, **kwargs): if 'width' in kwargs and 'height' in kwargs: size = Size(float(kwargs['width']), float(kwargs['height'])) elif 'width' in kwargs: width = float(kwargs['width']) size = Size(width, self.height * width / self.width) elif 'height' in kwargs: height = float(kwargs['height']) size = Size(self.width * height / self.height, height) else: size = self if 'scale' in kwargs: scale = float(kwargs['scale']) / 100 size = Size(size.width * scale, size.height * scale) return size def to_integer_point(self): return Size(int(self.width), int(self.height)) class Box(list): mapper = dict(x1=0, y1=1, x2=2, y2=3, x=0, y=1) def __init__(self, x1, y1, x2, y2): super(Box, self).__init__((x1, y1, x2, y2)) def __getattr__(self, name): try: return self[self.mapper[name]] except KeyError: raise AttributeError(name) def __repr__(self): _format = "<%s (%s, %s) %dx%d at 0x%08x>" params = (self.__class__.__name__, self.x1, self.y1, self.width, self.height, id(self)) return _format % params def shift(self, x=0, y=0): return self.__class__(self.x1 + x, self.y1 + y, self.x2 + x, self.y2 + y) def get_padding_for(self, size, **kwargs): valign = kwargs.get('valign', 'center') halign = kwargs.get('halign', 'center') padding = kwargs.get('padding', 0) if halign == 'left': dx = padding elif halign == 'right': dx = self.size.width - size.width - padding else: dx = int(math.ceil((self.size.width - size.width) / 2.0)) if valign == 'top': dy = padding elif valign == 'bottom': dy = self.size.height - size.height - padding else: dy = int(math.ceil((self.size.height - size.height) / 2.0)) return dx, dy @property def size(self): return Size(self.width, self.height) @property def width(self): return self.x2 - self.x1 @property def height(self): return self.y2 - self.y1 @property def topleft(self): return XY(self.x1, self.y1) @property def top(self): return XY(self.x1 + self.width // 2, self.y1) @property def topright(self): return XY(self.x2, self.y1) @property def bottomleft(self): return XY(self.x1, self.y2) @property def bottom(self): return XY(self.x1 + self.width // 2, self.y2) @property def bottomright(self): return XY(self.x2, self.y2) @property def left(self): return XY(self.x1, self.y1 + self.height // 2) @property def right(self): return XY(self.x2, self.y1 + self.height // 2) @property def center(self): return XY(self.x1 + self.width // 2, self.y1 + self.height // 2) def to_integer_point(self): return Box(*[int(i) for i in self]) def unquote(string): """ Remove quotas from string """ if string: m = re.match('\A(?P"|\')((.|\s)*)(?P=quote)\Z', string, re.M) if m: return re.sub("\\\\" + m.group(1), m.group(1), m.group(2)) else: return string else: return string def is_Pillow_available(): try: from PIL import _imagingft _imagingft return True except ImportError: return False ��������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/bootstrap.py����������������������������������������������������0000644�0000766�0000024�00000024176�12433520540�022505� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import re import sys import traceback from optparse import OptionParser, SUPPRESS_HELP from blockdiag import imagedraw from blockdiag import plugins from blockdiag.utils import images from blockdiag.utils.compat import codecs from blockdiag.utils.config import ConfigParser from blockdiag.utils.fontmap import parse_fontpath, FontMap from blockdiag.utils.logging import warning, error class Application(object): module = None options = None def __init__(self): self.cleanup_handlers = [] def __enter__(self): self.setup() return self def __exit__(self, *args): self.cleanup() def register_cleanup_handler(self, handler): self.cleanup_handlers.append(handler) def run(self, args): try: self.parse_options(args) self.create_fontmap() self.setup() parsed = self.parse_diagram() return self.build_diagram(parsed) except SystemExit as e: return e except UnicodeEncodeError as e: error("UnicodeEncodeError caught (check your font settings)") return -1 except Exception as e: if self.options and self.options.debug: traceback.print_exc() else: error("%s" % e) return -1 finally: self.cleanup() def parse_options(self, args): self.options = Options(self.module).parse(args) def create_fontmap(self): self.fontmap = create_fontmap(self.options) def setup(self): images.setup(self) plugins.setup(self) def parse_diagram(self): if self.options.input == '-': stream = codecs.getreader('utf-8-sig')(sys.stdin) self.code = stream.read() else: fp = codecs.open(self.options.input, 'r', 'utf-8-sig') self.code = fp.read() return self.module.parser.parse_string(self.code) def build_diagram(self, tree): ScreenNodeBuilder = self.module.builder.ScreenNodeBuilder try: diagram = ScreenNodeBuilder.build(tree, self.options) except: diagram = ScreenNodeBuilder.build(tree) # old interface DiagramDraw = self.module.drawer.DiagramDraw drawer = DiagramDraw(self.options.type, diagram, self.options.output, fontmap=self.fontmap, code=self.code, antialias=self.options.antialias, nodoctype=self.options.nodoctype, transparency=self.options.transparency) drawer.draw() if self.options.size: drawer.save(size=self.options.size) else: drawer.save() return 0 def cleanup(self): for handler in self.cleanup_handlers[:]: try: handler() except Exception as exc: error("%s" % exc) finally: self.cleanup_handlers.remove(handler) class Options(object): def __init__(self, module): self.module = module self.build_parser() def parse(self, args): self.options, self.args = self.parser.parse_args(args) self.validate() self.read_configfile() return self.options def build_parser(self): version = "%%prog %s" % self.module.__version__ usage = "usage: %prog [options] infile" self.parser = p = OptionParser(usage=usage, version=version) p.add_option('-a', '--antialias', action='store_true', help='Pass diagram image to anti-alias filter') p.add_option('-c', '--config', help='read configurations from FILE', metavar='FILE') p.add_option('--debug', action='store_true', help='Enable debug mode') p.add_option('-o', dest='output', help='write diagram to FILE', metavar='FILE') p.add_option('-f', '--font', default=[], action='append', help='use FONT to draw diagram', metavar='FONT') p.add_option('--fontmap', help='use FONTMAP file to draw diagram', metavar='FONT') p.add_option('--ignore-pil', dest='ignore_pil', default=False, action='store_true', help=SUPPRESS_HELP) p.add_option('--no-transparency', dest='transparency', default=True, action='store_false', help='do not make transparent background of diagram ' + '(PNG only)') p.add_option('--size', help='Size of diagram (ex. 320x240)') p.add_option('-T', dest='type', default='PNG', help='Output diagram as TYPE format') p.add_option('--nodoctype', action='store_true', help='Do not output doctype definition tags (SVG only)') return p def validate(self): if len(self.args) == 0: self.parser.print_help() sys.exit(0) self.options.input = self.args.pop(0) if self.options.output: pass elif self.options.output == '-': self.options.output = 'output.' + self.options.type.lower() else: basename = os.path.splitext(self.options.input)[0] ext = '.%s' % self.options.type.lower() self.options.output = basename + ext self.options.type = self.options.type.upper() try: imagedraw.create(self.options.type, None, debug=self.options.debug) except: msg = "unknown format: %s" % self.options.type raise RuntimeError(msg) if self.options.size: matched = re.match('^(\d+)x(\d+)$', self.options.size) if matched: self.options.size = [int(n) for n in matched.groups()] else: msg = "--size option must be formatted as WIDTHxHEIGHT." raise RuntimeError(msg) if self.options.type == 'PDF': try: import reportlab.pdfgen.canvas reportlab.pdfgen.canvas except ImportError: msg = "could not output PDF format; Install reportlab." raise RuntimeError(msg) if self.options.ignore_pil: warning("--ignore-pil option is deprecated " "(detect automatically).") if self.options.nodoctype and self.options.type != 'SVG': msg = "--nodoctype option work in SVG images." raise RuntimeError(msg) if self.options.transparency is False and self.options.type != 'PNG': msg = "--no-transparency option work in PNG images." raise RuntimeError(msg) if self.options.config and not os.path.isfile(self.options.config): msg = "config file is not found: %s" % self.options.config raise RuntimeError(msg) if self.options.fontmap and not os.path.isfile(self.options.fontmap): msg = "fontmap file is not found: %s" % self.options.fontmap raise RuntimeError(msg) def read_configfile(self): if self.options.config: configpath = self.options.config elif os.environ.get('HOME'): configpath = '%s/.blockdiagrc' % os.environ.get('HOME') elif os.environ.get('USERPROFILE'): configpath = '%s/.blockdiagrc' % os.environ.get('USERPROFILE') else: configpath = '' appname = self.module.__name__ if os.path.isfile(configpath): config = ConfigParser() config.read(configpath) if config.has_option(appname, 'fontpath'): fontpath = config.get(appname, 'fontpath') self.options.font.append(fontpath) if config.has_option(appname, 'fontmap'): if self.options.fontmap is None: self.options.fontmap = config.get(appname, 'fontmap') if config.has_option(appname, 'antialias'): antialias = config.get(appname, 'antialias') if antialias.lower() == 'true': self.options.antialias = True if self.options.fontmap is None: self.options.fontmap = configpath def detectfont(options): import glob fontdirs = [ '/usr/share/fonts', '/Library/Fonts', '/System/Library/Fonts', 'c:/windows/fonts', '/usr/local/share/font-*', ] fontfiles = [ 'ipagp.ttf', 'ipagp.otf', 'VL-PGothic-Regular.ttf', 'Hiragino Sans GB W3.otf', 'AppleGothic.ttf', 'msgothic.ttf', 'msgoth04.ttf', 'msgothic.ttc', ] fontpath = None if options.font: for path in options.font: _path, _ = parse_fontpath(path) if os.path.isfile(_path): fontpath = path break else: msg = 'fontfile is not found: %s' % options.font raise RuntimeError(msg) if fontpath is None: globber = (glob.glob(d) for d in fontdirs) for fontdir in sum(globber, []): for root, _, files in os.walk(fontdir): for font in fontfiles: if font in files: fontpath = os.path.join(root, font) break return fontpath def create_fontmap(options): fontmap = FontMap(options.fontmap) if fontmap.find().path is None or options.font: fontpath = detectfont(options) fontmap.set_default_font(fontpath) return fontmap ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/compat.py�������������������������������������������������������0000644�0000766�0000024�00000004145�12472050741�021751� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import codecs if sys.version_info[0] == 2: string_types = (str, unicode) # NOQA: pyflakes complains to unicode in py3 from urllib import urlopen # NOQA: exporting for common interface else: string_types = (str,) from urllib.request import urlopen # NOQA: exporting for common interface def u(string): if sys.version_info[0] == 2: return unicode(string, "unicode_escape") # NOQA: pyflakes complains to unicode in py3 else: return string # replace codecs.getreader if sys.version_info[0] == 3: getreader = codecs.getreader def py3_getreader(encoding): return lambda stream, *args: getreader(encoding)(stream.buffer, *args) codecs.getreader = py3_getreader def cmp_to_key(mycmp): """Convert a cmp= function into a key= function""" class K(object): __slots__ = ['obj'] def __init__(self, obj, *args): self.obj = obj def __lt__(self, other): return mycmp(self.obj, other.obj) < 0 def __gt__(self, other): return mycmp(self.obj, other.obj) > 0 def __eq__(self, other): return mycmp(self.obj, other.obj) == 0 def __le__(self, other): return mycmp(self.obj, other.obj) <= 0 def __ge__(self, other): return mycmp(self.obj, other.obj) >= 0 def __ne__(self, other): return mycmp(self.obj, other.obj) != 0 def __hash__(self): raise TypeError('hash not implemented') return K ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/config.py�������������������������������������������������������0000644�0000766�0000024�00000002502�12217202617�021724� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import io import sys try: from configparser import SafeConfigParser except ImportError: from ConfigParser import SafeConfigParser class ConfigParser(SafeConfigParser): def __init__(self): if sys.version_info > (2, 6) and sys.version_info < (2, 7): # only for Python2.6 # - dict_type argument is supported py2.6 or later # - SafeConfigParser of py2.7 uses OrderedDict as default from ordereddict import OrderedDict SafeConfigParser.__init__(self, dict_type=OrderedDict) else: SafeConfigParser.__init__(self) def read(self, path): fd = io.open(path, 'r', encoding='utf-8-sig') self.readfp(fd) fd.close() ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/fontmap.py������������������������������������������������������0000644�0000766�0000024�00000012073�12472044421�022127� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import re import os import copy from collections import namedtuple from blockdiag.utils.config import ConfigParser from blockdiag.utils.logging import warning def parse_fontpath(path): if path is None: return (None, None) match = re.search('^(.*):(\d)$', path) if match: return (match.group(1), int(match.group(2))) else: return (path, None) class FontInfo(object): def __init__(self, family, path, size): self.path = path self.size = int(size) family = self._parse(family) self.name = family[0] self.generic_family = family[1] self.weight = family[2] self.style = family[3] def __repr__(self): return ("" % (self.familyname, self.size)) @property def familyname(self): if self.name: name = self.name + "-" else: name = '' if self.generic_family == 'sans-serif': generic_family = 'sansserif' else: generic_family = self.generic_family if self.weight == 'bold': return "%s%s-%s" % (name, generic_family, self.weight) else: return "%s%s-%s" % (name, generic_family, self.style) def _parse(self, familyname): pattern = '^(?:(.*)-)?' + \ '(serif|sansserif|monospace|fantasy|cursive)' + \ '(?:-(normal|bold|italic|oblique))?$' match = re.search(pattern, familyname or '') if match is None: msg = 'Unknown font family: %s' % familyname raise AttributeError(msg) name = match.group(1) or '' generic_family = match.group(2) style = match.group(3) or '' if generic_family == 'sansserif': generic_family = 'sans-serif' if style == 'bold': weight = 'bold' style = 'normal' elif style in ('italic', 'oblique'): weight = 'normal' style = style else: weight = 'normal' style = 'normal' return [name, generic_family, weight, style] def duplicate(self): return copy.copy(self) class FontMap(object): BASE_FONTSIZE = 11 fontsize = BASE_FONTSIZE default_fontfamily = 'sansserif' def __init__(self, filename=None): self.fonts = {} self.aliases = {} if filename: self._parse_config(filename) self.set_default_font(None) def set_default_fontfamily(self, fontfamily): self.default_fontfamily = fontfamily self.set_default_font(None) def _parse_config(self, conffile): config = ConfigParser() if hasattr(conffile, 'read'): config.readfp(conffile) elif os.path.isfile(conffile): config.read(conffile) else: msg = "fontmap file is not found: %s" % conffile raise RuntimeError(msg) if config.has_section('fontmap'): for name, path in config.items('fontmap'): self.append_font(name, path) if config.has_section('fontalias'): for name, family in config.items('fontalias'): self.aliases[name] = family def set_default_font(self, path): if path is None and self.find() is not None: return self.append_font(self.default_fontfamily, path) def append_font(self, fontfamily, path): _path, _ = parse_fontpath(path) if path is None or os.path.isfile(_path): font = FontInfo(fontfamily, path, self.fontsize) self.fonts[font.familyname] = font else: warning('fontfile `%s` is not found: %s', fontfamily, path) def _regulate_familyname(self, name): return FontInfo(name, None, self.BASE_FONTSIZE).familyname.lower() def find(self, element=None): fontfamily = getattr(element, 'fontfamily', None) or \ self.default_fontfamily fontfamily = self.aliases.get(fontfamily, fontfamily) fontsize = getattr(element, 'fontsize', None) or self.fontsize name = self._regulate_familyname(fontfamily) if name in self.fonts: font = self.fonts[name].duplicate() font.size = fontsize elif element is not None: warning("Unknown fontfamily: %s", fontfamily) elem = namedtuple('Font', 'fontsize')(fontsize) font = self.find(elem) else: font = None return font ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/images.py�������������������������������������������������������0000644�0000766�0000024�00000010013�12500766212�021721� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import division import io import os import re from PIL import Image from tempfile import NamedTemporaryFile from blockdiag.utils import urlutil from blockdiag.utils.compat import u from blockdiag.utils.logging import warning urlopen_cache = {} def urlopen(url, *args, **kwargs): """ auto caching urlopen() (using tempfile) """ from blockdiag.utils.compat import urlopen as orig_urlopen if url not in urlopen_cache: with NamedTemporaryFile(delete=False) as tmpfile: tmpfile.write(orig_urlopen(url, *args, **kwargs).read()) tmpfile.flush() urlopen_cache[url] = tmpfile.name return io.open(urlopen_cache[url], 'rb') def get_image_size(image): if isinstance(image, Image.Image): return image.size else: stream = None try: stream = open(image) return stream.size finally: if stream and hasattr(stream, 'close'): stream.close() def calc_image_size(size, bounded): if bounded[0] < size[0] or bounded[1] < size[1]: if (size[0] * 1.0 // bounded[0]) < (size[1] * 1.0 // bounded[1]): size = (size[0] * bounded[1] // size[1], bounded[1]) else: size = (bounded[0], size[1] * bounded[0] // size[0]) return size def color_to_rgb(color): import webcolors if color == 'none' or isinstance(color, (list, tuple)): rgb = color elif re.match('#', color): rgb = webcolors.hex_to_rgb(color) else: rgb = webcolors.name_to_rgb(color) return rgb def wand_open(url, stream): try: import wand.image except: warning("unknown image type: %s", url) raise IOError try: png_image = io.BytesIO() with wand.image.Image(file=stream) as img: img.format = 'PNG' img.save(file=png_image) png_image.seek(0) return png_image except Exception as exc: warning("Fail to convert %s to PNG: %r", url, exc) raise IOError def pillow_open(url, stream): try: if isinstance(url, Image.Image): return url else: return Image.open(stream) except IOError: stream.seek(0) png_stream = wand_open(url, stream) return Image.open(png_stream) def open(url, mode='Pillow'): if hasattr(url, 'read') or isinstance(url, Image.Image): stream = url elif not urlutil.isurl(url): stream = io.open(url, 'rb') else: try: # wrap BytesIO for rewind stream stream = io.BytesIO(urlopen(url).read()) except: warning(u("Could not retrieve: %s"), url) raise IOError image = pillow_open(url, stream) if mode.lower() == 'pillow': # stream will be closed by GC return image else: # mode == 'png' try: png_image = io.BytesIO() image.save(png_image, 'PNG') if hasattr(stream, 'close'): # close() is implemented on Pillow stream.close() except: warning(u("Could not convert image: %s"), url) raise IOError png_image.seek(0) return png_image def cleanup(): for url in list(urlopen_cache.keys()): path = urlopen_cache.pop(url) try: os.remove(path) except: pass def setup(app): app.register_cleanup_handler(cleanup) ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/logging.py������������������������������������������������������0000644�0000766�0000024�00000001617�12350774131�022116� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys from blockdiag.utils.compat import u def warning(msg, *args): sys.stderr.write(u("WARNING: ")) sys.stderr.write(msg % args) sys.stderr.write(u("\n")) def error(msg, *args): sys.stderr.write(u("ERROR: ")) sys.stderr.write(msg % args) sys.stderr.write(u("\n")) �����������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/myitertools.py��������������������������������������������������0000644�0000766�0000024�00000002360�12227702767�023067� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from itertools import cycle def istep(seq, step=2): iterable = iter(seq) while True: yield [next(iterable) for _ in range(step)] def stepslice(iterable, steps): iterable = iter(iterable) step = cycle(steps) while True: # skip (1) n = next(step) if n == 0: pass elif n == 1: o = next(iterable) yield o yield o else: yield next(iterable) for _ in range(n - 2): next(iterable) yield next(iterable) # skip (2) for _ in range(next(step)): next(iterable) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/rst/������������������������������������������������������������0000755�0000766�0000024�00000000000�12556430040�020715� 5����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/rst/__init__.py�������������������������������������������������0000644�0000766�0000024�00000001144�11655736213�023040� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/rst/directives.py�����������������������������������������������0000644�0000766�0000024�00000033056�12433520573�023444� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import io from hashlib import sha1 from functools import wraps from collections import namedtuple from docutils import nodes from docutils.parsers import rst from docutils.parsers.rst.roles import set_classes from docutils.statemachine import ViewList from blockdiag.utils.bootstrap import create_fontmap, Application from blockdiag.utils.compat import string_types from blockdiag.utils.rst.nodes import blockdiag as blockdiag_node directive_options_default = dict(format='PNG', antialias=False, fontpath=None, outputdir=None, nodoctype=False, noviewbox=False, inline_svg=False) directive_options = {} def relfn2path(env, filename): if filename.startswith('/') or filename.startswith(os.sep): relfn = filename[1:] else: path = env.doc2path(env.docname, base=None) relfn = os.path.join(os.path.dirname(path), filename) return relfn, os.path.join(env.srcdir, relfn) def with_blockdiag(fn): @wraps(fn) def decorator(*args): with Application(): return fn(*args) return decorator def align(argument): align_values = ('left', 'center', 'right') return rst.directives.choice(argument, align_values) def figwidth_value(argument): if argument.lower() == 'image': return 'image' else: return rst.directives.length_or_percentage_or_unitless(argument, 'px') class BlockdiagDirectiveBase(rst.Directive): """ Directive to insert arbitrary dot markup. """ name = "blockdiag" node_class = blockdiag_node has_content = True required_arguments = 0 optional_arguments = 1 final_argument_whitespace = False option_spec = { 'alt': rst.directives.unchanged, 'height': rst.directives.length_or_unitless, 'width': rst.directives.length_or_percentage_or_unitless, 'scale': rst.directives.percentage, 'align': align, 'caption': rst.directives.unchanged, 'desctable': rst.directives.flag, 'maxwidth': rst.directives.nonnegative_int, # deprecated 'name': rst.directives.unchanged, 'class': rst.directives.class_option, 'figwidth': figwidth_value, 'figclass': rst.directives.class_option, } def run(self): if self.arguments: document = self.state.document if self.content: msg = ('%s directive cannot have both content and ' 'a filename argument' % self.name) return [document.reporter.warning(msg, line=self.lineno)] try: filename = self.source_filename(self.arguments[0]) fp = io.open(filename, 'r', encoding='utf-8-sig') try: dotcode = fp.read() finally: fp.close() except (IOError, OSError): msg = 'External %s file %r not found or reading it failed' % \ (self.name, filename) return [document.reporter.warning(msg, line=self.lineno)] else: dotcode = '\n'.join(self.content).strip() if not dotcode: msg = 'Ignoring "%s" directive without content.' % self.name return [self.state_machine.reporter.warning(msg, line=self.lineno)] set_classes(self.options) node = self.node_class() results = [node] node['code'] = dotcode node['caption'] = self.options.pop('caption', None) node['options'] = self.options # for sphinxcontrib.* module (backward compatibility) node['alt'] = self.options.get('alt') # replace maxwidth option to width (backward compatibility) if 'maxwidth' in node['options']: node['options']['width'] = str(node['options']['maxwidth']) msg = ':maxwidth: option is deprecated. Use :width: option.' warning = self.state_machine.reporter.warning(msg, line=self.lineno) results.append(warning) return results def source_filename(self, filename): if hasattr(self.state.document.settings, 'env'): env = self.state.document.settings.env rel_filename, filename = relfn2path(env, self.arguments[0]) env.note_dependency(rel_filename) return filename class BlockdiagDirective(BlockdiagDirectiveBase): processor = None # backward compatibility for 1.4.0 @with_blockdiag def run(self): figwidth = self.options.pop('figwidth', None) figclasses = self.options.pop('figclass', None) if self.options.get('caption'): align = self.options.pop('align', None) else: align = None results = super(BlockdiagDirective, self).run() node = results[0] if not isinstance(node, self.node_class): return results try: diagram = self.node2diagram(node) except Exception as e: raise self.warning(str(e)) if 'desctable' in node['options']: results += self.description_tables(diagram) results[0] = self.node2image(node, diagram) self.add_name(results[0]) if node.get('caption'): elem = nodes.Element() self.state.nested_parse(ViewList([node['caption']], source=''), self.content_offset, elem) caption_node = nodes.caption(elem[0].rawsource, '', *elem[0].children) caption_node.source = elem[0].source caption_node.line = elem[0].line fig = nodes.figure() fig += results[0] fig += caption_node if figwidth == 'image': width = self.get_actual_width(node, diagram) fig['width'] = str(width) + 'px' elif figwidth is not None: fig['width'] = figwidth if figclasses: fig['classes'] += figclasses if align: fig['align'] = align results[0] = fig return results @property def global_options(self): return directive_options def node2diagram(self, node): if hasattr(node, 'to_diagram'): return node.to_diagram() else: try: tree = self.processor.parser.parse_string(node['code']) except: code = '%s { %s }' % (self.name, node['code']) tree = self.processor.parser.parse_string(code) node['code'] = code # replace if succeeded return self.processor.builder.ScreenNodeBuilder.build(tree) def get_actual_width(self, node, diagram): fontmap = self.create_fontmap() if hasattr(node, 'to_drawer'): drawer = node.to_drawer('SVG', None, fontmap, **self.global_options) else: drawer = self.processor.drawer.DiagramDraw('SVG', diagram, None, fontmap=fontmap) return drawer.pagesize()[0] def node2image(self, node, diagram): _format = self.global_options['format'].lower() if _format == 'svg' and self.global_options['inline_svg'] is True: return self.node2image_inline_svg(node, diagram) filename = self.image_filename(node) fontmap = self.create_fontmap() if hasattr(node, 'to_drawer'): drawer = node.to_drawer(_format, filename, fontmap, **self.global_options) else: drawer = self.processor.drawer.DiagramDraw(_format, diagram, filename, fontmap=fontmap, **self.global_options) if not os.path.isfile(filename): drawer.draw() drawer.save() return nodes.image(uri=filename, **node['options']) def node2image_inline_svg(self, node, diagram): fontmap = self.create_fontmap() if hasattr(node, 'to_drawer'): drawer = node.to_drawer('SVG', None, fontmap, **self.global_options) else: drawer = self.processor.drawer.DiagramDraw('svg', diagram, None, fontmap=fontmap, **self.global_options) drawer.draw() size = drawer.pagesize().resize(**node['options']).to_integer_point() content = drawer.save(size) return nodes.raw('', content, format='html') def create_fontmap(self): Options = namedtuple('Options', 'font fontmap') fontpath = self.global_options['fontpath'] if isinstance(fontpath, (list, tuple)): options = Options(fontpath, None) elif isinstance(fontpath, string_types): options = Options([fontpath], None) else: options = Options([], None) return create_fontmap(options) def image_filename(self, node, prefix='', ext='png'): if hasattr(node, 'get_path'): return node.get_path(**self.global_options) else: options = dict(node['options']) options.update(font=self.global_options['fontpath'], antialias=self.global_options['antialias']) hashseed = (node['code'] + str(options)).encode('utf-8') hashed = sha1(hashseed).hexdigest() _format = self.global_options['format'].lower() outputdir = self.global_options['outputdir'] filename = "%s%s-%s.%s" % (self.name, prefix, hashed, _format) if outputdir: filename = os.path.join(outputdir, filename) return filename def description_tables(self, diagram): tables = [] desctable = self.node_description_table(diagram) if desctable: tables.append(desctable) desctable = self.edge_description_table(diagram) if desctable: tables.append(desctable) return tables def node_description_table(self, diagram): nodes = diagram.traverse_nodes() klass = diagram._DiagramNode widths = [25] + [50] * (len(klass.desctable) - 1) headers = [klass.attrname[n] for n in klass.desctable] def node_number(node): try: return int(node[0]) except (TypeError, ValueError): return 65535 descriptions = [n.to_desctable() for n in nodes if n.drawable] descriptions.sort(key=node_number) for i in reversed(range(len(headers))): if any(desc[i] for desc in descriptions): pass else: widths.pop(i) headers.pop(i) for desc in descriptions: desc.pop(i) if len(headers) == 1: return None else: return self._description_table(descriptions, widths, headers) def edge_description_table(self, diagram): edges = diagram.traverse_edges() widths = [25, 50] headers = ['Name', 'Description'] descriptions = [e.to_desctable() for e in edges if e.style != 'none'] if any(desc[1] for desc in descriptions): return self._description_table(descriptions, widths, headers) else: return None def _description_table(self, descriptions, widths, headers): # generate table-root tgroup = nodes.tgroup(cols=len(widths)) for width in widths: tgroup += nodes.colspec(colwidth=width) table = nodes.table() table += tgroup # generate table-header thead = nodes.thead() row = nodes.row() for header in headers: entry = nodes.entry() entry += nodes.paragraph(text=header) row += entry thead += row tgroup += thead # generate table-body tbody = nodes.tbody() for desc in descriptions: row = nodes.row() for attr in desc: entry = nodes.entry() if not isinstance(attr, string_types): attr = str(attr) self.state.nested_parse(ViewList([attr], source=attr), 0, entry) row += entry tbody += row tgroup += tbody return table def setup(**kwargs): global directive_options, directive_options_default for key, value in directive_options_default.items(): directive_options[key] = kwargs.get(key, value) rst.directives.register_directive("blockdiag", BlockdiagDirective) ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/rst/nodes.py����������������������������������������������������0000644�0000766�0000024�00000003624�12360215311�022377� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os from hashlib import sha1 from docutils import nodes import blockdiag.parser import blockdiag.builder import blockdiag.drawer class blockdiag(nodes.General, nodes.Element): name = 'blockdiag' processor = blockdiag def to_diagram(self): try: tree = self.processor.parser.parse_string(self['code']) except: code = '%s { %s }' % (self.name, self['code']) tree = self.processor.parser.parse_string(code) self['code'] = code # replace if succeeded return self.processor.builder.ScreenNodeBuilder.build(tree) def to_drawer(self, image_format, filename, fontmap, **kwargs): diagram = self.to_diagram() return self.processor.drawer.DiagramDraw(image_format, diagram, filename, fontmap=fontmap, **kwargs) def get_path(self, **options): options.update(self['options']) hashseed = (self['code'] + str(options)).encode('utf-8') hashed = sha1(hashseed).hexdigest() filename = "%s-%s.%s" % (self.name, hashed, options['format'].lower()) outputdir = options.get('outputdir') if outputdir: filename = os.path.join(outputdir, filename) return filename ������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/urlutil.py������������������������������������������������������0000644�0000766�0000024�00000001530�12310534756�022165� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. try: import urlparse except ImportError: import urllib.parse as urlparse def isurl(url): o = urlparse.urlparse(url) accpetable = ["http", "https"] if o[0] in accpetable: return True else: return False ������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag/utils/uuid.py���������������������������������������������������������0000644�0000766�0000024�00000001454�12231365410�021427� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. try: from uuid import uuid1 uuid = uuid1 except ImportError: from random import random uuid = random from blockdiag.utils.compat import u def generate(): return u(str(uuid())) ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag.egg-info/�������������������������������������������������������������0000755�0000766�0000024�00000000000�12556430037�020445� 5����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag.egg-info/dependency_links.txt�����������������������������������������0000644�0000766�0000024�00000000001�12556430033�024507� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag.egg-info/entry_points.txt���������������������������������������������0000644�0000766�0000024�00000002723�12556430033�023743� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������ [console_scripts] blockdiag = blockdiag.command:main [blockdiag_noderenderer] box = blockdiag.noderenderer.box square = blockdiag.noderenderer.square roundedbox = blockdiag.noderenderer.roundedbox diamond = blockdiag.noderenderer.diamond minidiamond = blockdiag.noderenderer.minidiamond mail = blockdiag.noderenderer.mail note = blockdiag.noderenderer.note cloud = blockdiag.noderenderer.cloud circle = blockdiag.noderenderer.circle ellipse = blockdiag.noderenderer.ellipse beginpoint = blockdiag.noderenderer.beginpoint endpoint = blockdiag.noderenderer.endpoint actor = blockdiag.noderenderer.actor flowchart.database = blockdiag.noderenderer.flowchart.database flowchart.input = blockdiag.noderenderer.flowchart.input flowchart.loopin = blockdiag.noderenderer.flowchart.loopin flowchart.loopout = blockdiag.noderenderer.flowchart.loopout flowchart.terminator = blockdiag.noderenderer.flowchart.terminator textbox = blockdiag.noderenderer.textbox dots = blockdiag.noderenderer.dots none = blockdiag.noderenderer.none [blockdiag_plugins] attributes = blockdiag.plugins.attributes autoclass = blockdiag.plugins.autoclass [blockdiag_imagedrawers] imagedraw_png = blockdiag.imagedraw.png imagedraw_svg = blockdiag.imagedraw.svg imagedraw_pdf = blockdiag.imagedraw.pdf ���������������������������������������������blockdiag-1.5.3/src/blockdiag.egg-info/PKG-INFO�����������������������������������������������������0000644�0000766�0000024�00000011326�12556430033�021541� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������Metadata-Version: 1.1 Name: blockdiag Version: 1.5.3 Summary: blockdiag generates block-diagram image from text Home-page: http://blockdiag.com/ Author: Takeshi Komiya Author-email: i.tkomiya at gmail.com License: Apache License 2.0 Download-URL: http://pypi.python.org/pypi/blockdiag Description: `blockdiag` generate block-diagram image file from spec-text file. .. image:: https://drone.io/bitbucket.org/blockdiag/blockdiag/status.png :target: https://drone.io/bitbucket.org/blockdiag/blockdiag :alt: drone.io CI build status .. image:: https://pypip.in/v/blockdiag/badge.png :target: https://pypi.python.org/pypi/blockdiag/ :alt: Latest PyPI version .. image:: https://pypip.in/d/blockdiag/badge.png :target: https://pypi.python.org/pypi/blockdiag/ :alt: Number of PyPI downloads Features ======== * Generate block-diagram from dot like text (basic feature). * Multilingualization for node-label (utf-8 only). You can get some examples and generated images on `blockdiag.com `_ . Setup ===== Use easy_install or pip:: $ sudo easy_install blockdiag Or $ sudo pip install blockdiag If you want to export as PDF format, give pdf arguments:: $ sudo easy_install "blockdiag[pdf]" Copy and modify ini file. example:: $ cp /blockdiag/examples/simple.diag . $ vi simple.diag Please refer to `spec-text setting sample`_ section for the format of the `simpla.diag` configuration file. spec-text setting sample ======================== Few examples are available. You can get more examples at `blockdiag.com`_ . simple.diag ------------ simple.diag is simply define nodes and transitions by dot-like text format:: diagram admin { top_page -> config -> config_edit -> config_confirm -> top_page; } screen.diag ------------ screen.diag is more complexly sample. diaglam nodes have a alternative label and some transitions:: diagram admin { top_page [label = "Top page"]; foo_index [label = "List of FOOs"]; foo_detail [label = "Detail FOO"]; foo_add [label = "Add FOO"]; foo_add_confirm [label = "Add FOO (confirm)"]; foo_edit [label = "Edit FOO"]; foo_edit_confirm [label = "Edit FOO (confirm)"]; foo_delete_confirm [label = "Delete FOO (confirm)"]; bar_detail [label = "Detail of BAR"]; bar_edit [label = "Edit BAR"]; bar_edit_confirm [label = "Edit BAR (confirm)"]; logout; top_page -> foo_index; top_page -> bar_detail; foo_index -> foo_detail; foo_detail -> foo_edit; foo_detail -> foo_delete_confirm; foo_index -> foo_add -> foo_add_confirm -> foo_index; foo_index -> foo_edit -> foo_edit_confirm -> foo_index; foo_index -> foo_delete_confirm -> foo_index; bar_detail -> bar_edit -> bar_edit_confirm -> bar_detail; } Usage ===== Execute blockdiag command:: $ blockdiag simple.diag $ ls simple.png simple.png Requirements ============ * Python 2.6, 2.7, 3.2, 3.3, 3.4 * Pillow 2.2.1 or later * funcparserlib 0.3.6 or later * reportlab (optional) * wand and imagemagick (optional) * setuptools License ======= Apache License 2.0 Keywords: diagram,generator Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Documentation Classifier: Topic :: Text Processing :: Markup ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag.egg-info/requires.txt�������������������������������������������������0000644�0000766�0000024�00000000175�12556430033�023044� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������setuptools funcparserlib Pillow webcolors [pdf] reportlab [rst] docutils [testing] nose mock pep8>=1.3 reportlab docutils ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag.egg-info/SOURCES.txt��������������������������������������������������0000644�0000766�0000024�00000026347�12556430033�022341� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������.hgignore .hgtags .installed.cfg CHANGES.rst LICENSE MANIFEST.in README.rst a.diag b.diag b.png blockdiag.1 blockdiag.svg bootstrap.py bootstrap.pyc buildout.cfg c.diag e.diag setup.cfg setup.py test.sh tox.ini examples/blockdiagrc examples/group.diag examples/group.png examples/group.svg examples/multibyte.diag examples/multibyte.png examples/multibyte.svg examples/numbered.diag examples/numbered.png examples/numbered.svg examples/screen.diag examples/screen.png examples/screen.svg examples/simple.diag examples/simple.png examples/simple.svg src/blockdiag_sphinxhelper.py src/blockdiag/__init__.py src/blockdiag/builder.py src/blockdiag/command.py src/blockdiag/drawer.py src/blockdiag/elements.py src/blockdiag/metrics.py src/blockdiag/parser.py src/blockdiag.egg-info/PKG-INFO src/blockdiag.egg-info/SOURCES.txt src/blockdiag.egg-info/dependency_links.txt src/blockdiag.egg-info/entry_points.txt src/blockdiag.egg-info/requires.txt src/blockdiag.egg-info/top_level.txt src/blockdiag/imagedraw/__init__.py src/blockdiag/imagedraw/base.py src/blockdiag/imagedraw/pdf.py src/blockdiag/imagedraw/png.py src/blockdiag/imagedraw/simplesvg.py src/blockdiag/imagedraw/svg.py src/blockdiag/imagedraw/textfolder.py src/blockdiag/imagedraw/filters/__init__.py src/blockdiag/imagedraw/filters/linejump.py src/blockdiag/imagedraw/utils/__init__.py src/blockdiag/imagedraw/utils/ellipse.py src/blockdiag/imagedraw/utils/pillow.py src/blockdiag/noderenderer/__init__.py src/blockdiag/noderenderer/actor.py src/blockdiag/noderenderer/base.py src/blockdiag/noderenderer/beginpoint.py src/blockdiag/noderenderer/box.py src/blockdiag/noderenderer/circle.py src/blockdiag/noderenderer/cloud.py src/blockdiag/noderenderer/diamond.py src/blockdiag/noderenderer/dots.py src/blockdiag/noderenderer/ellipse.py src/blockdiag/noderenderer/endpoint.py src/blockdiag/noderenderer/mail.py src/blockdiag/noderenderer/minidiamond.py src/blockdiag/noderenderer/none.py src/blockdiag/noderenderer/note.py src/blockdiag/noderenderer/roundedbox.py src/blockdiag/noderenderer/square.py src/blockdiag/noderenderer/textbox.py src/blockdiag/noderenderer/flowchart/__init__.py src/blockdiag/noderenderer/flowchart/database.py src/blockdiag/noderenderer/flowchart/input.py src/blockdiag/noderenderer/flowchart/loopin.py src/blockdiag/noderenderer/flowchart/loopout.py src/blockdiag/noderenderer/flowchart/terminator.py src/blockdiag/plugins/__init__.py src/blockdiag/plugins/attributes.py src/blockdiag/plugins/autoclass.py src/blockdiag/tests/__init__.py src/blockdiag/tests/test_boot_params.py src/blockdiag/tests/test_builder.py src/blockdiag/tests/test_builder_edge.py src/blockdiag/tests/test_builder_errors.py src/blockdiag/tests/test_builder_group.py src/blockdiag/tests/test_builder_node.py src/blockdiag/tests/test_builder_separate.py src/blockdiag/tests/test_command.py src/blockdiag/tests/test_generate_diagram.py src/blockdiag/tests/test_imagedraw_textfolder.py src/blockdiag/tests/test_imagedraw_utils.py src/blockdiag/tests/test_parser.py src/blockdiag/tests/test_utils.py src/blockdiag/tests/test_utils_fontmap.py src/blockdiag/tests/utils.py src/blockdiag/tests/VLGothic/LICENSE src/blockdiag/tests/VLGothic/LICENSE.en src/blockdiag/tests/VLGothic/LICENSE_E.mplus src/blockdiag/tests/VLGothic/LICENSE_J.mplus src/blockdiag/tests/VLGothic/VL-Gothic-Regular.ttf src/blockdiag/tests/diagrams/README src/blockdiag/tests/diagrams/auto_jumping_edge.diag src/blockdiag/tests/diagrams/background_url_image.diag src/blockdiag/tests/diagrams/beginpoint_color.diag src/blockdiag/tests/diagrams/branched.diag src/blockdiag/tests/diagrams/circular_ref.diag src/blockdiag/tests/diagrams/circular_ref2.diag src/blockdiag/tests/diagrams/circular_ref_and_parent_node.diag src/blockdiag/tests/diagrams/circular_ref_to_root.diag src/blockdiag/tests/diagrams/circular_skipped_edge.diag src/blockdiag/tests/diagrams/debian-logo-256color-palettealpha.png src/blockdiag/tests/diagrams/define_class.diag src/blockdiag/tests/diagrams/diagram_attributes.diag src/blockdiag/tests/diagrams/diagram_attributes_order.diag src/blockdiag/tests/diagrams/diagram_orientation.diag src/blockdiag/tests/diagrams/edge_attribute.diag src/blockdiag/tests/diagrams/edge_datamodels.diag src/blockdiag/tests/diagrams/edge_label.diag src/blockdiag/tests/diagrams/edge_layout_landscape.diag src/blockdiag/tests/diagrams/edge_layout_portrait.diag src/blockdiag/tests/diagrams/edge_shape.diag src/blockdiag/tests/diagrams/edge_styles.diag src/blockdiag/tests/diagrams/empty_group.diag src/blockdiag/tests/diagrams/empty_group_declaration.diag src/blockdiag/tests/diagrams/empty_nested_group.diag src/blockdiag/tests/diagrams/endpoint_color.diag src/blockdiag/tests/diagrams/flowable_node.diag src/blockdiag/tests/diagrams/folded_edge.diag src/blockdiag/tests/diagrams/group_and_skipped_edge.diag src/blockdiag/tests/diagrams/group_attribute.diag src/blockdiag/tests/diagrams/group_children_height.diag src/blockdiag/tests/diagrams/group_children_order.diag src/blockdiag/tests/diagrams/group_children_order2.diag src/blockdiag/tests/diagrams/group_children_order3.diag src/blockdiag/tests/diagrams/group_children_order4.diag src/blockdiag/tests/diagrams/group_declare_as_node_attribute.diag src/blockdiag/tests/diagrams/group_height.diag src/blockdiag/tests/diagrams/group_id_and_node_id_are_not_conflicted.diag src/blockdiag/tests/diagrams/group_label.diag src/blockdiag/tests/diagrams/group_order.diag src/blockdiag/tests/diagrams/group_order2.diag src/blockdiag/tests/diagrams/group_order3.diag src/blockdiag/tests/diagrams/group_orientation.diag src/blockdiag/tests/diagrams/group_sibling.diag src/blockdiag/tests/diagrams/group_works_node_decorator.diag src/blockdiag/tests/diagrams/labeled_circular_ref.diag src/blockdiag/tests/diagrams/large_group_and_node.diag src/blockdiag/tests/diagrams/large_group_and_node2.diag src/blockdiag/tests/diagrams/large_group_and_two_nodes.diag src/blockdiag/tests/diagrams/merge_groups.diag src/blockdiag/tests/diagrams/multiple_groups.diag src/blockdiag/tests/diagrams/multiple_nested_groups.diag src/blockdiag/tests/diagrams/multiple_node_relation.diag src/blockdiag/tests/diagrams/multiple_nodes_definition.diag src/blockdiag/tests/diagrams/multiple_parent_node.diag src/blockdiag/tests/diagrams/nested_group_orientation.diag src/blockdiag/tests/diagrams/nested_group_orientation2.diag src/blockdiag/tests/diagrams/nested_groups.diag src/blockdiag/tests/diagrams/nested_groups_and_edges.diag src/blockdiag/tests/diagrams/nested_groups_work_node_decorator.diag src/blockdiag/tests/diagrams/nested_skipped_circular.diag src/blockdiag/tests/diagrams/node_attribute.diag src/blockdiag/tests/diagrams/node_attribute_and_group.diag src/blockdiag/tests/diagrams/node_has_multilined_label.diag src/blockdiag/tests/diagrams/node_height.diag src/blockdiag/tests/diagrams/node_icon.diag src/blockdiag/tests/diagrams/node_id_includes_dot.diag src/blockdiag/tests/diagrams/node_in_group_follows_outer_node.diag src/blockdiag/tests/diagrams/node_link.diag src/blockdiag/tests/diagrams/node_rotated_labels.diag src/blockdiag/tests/diagrams/node_shape.diag src/blockdiag/tests/diagrams/node_shape_background.diag src/blockdiag/tests/diagrams/node_shape_namespace.diag src/blockdiag/tests/diagrams/node_shape_with_none_shadow.diag src/blockdiag/tests/diagrams/node_shape_with_solid_shadow.diag src/blockdiag/tests/diagrams/node_style_dasharray.diag src/blockdiag/tests/diagrams/node_styles.diag src/blockdiag/tests/diagrams/node_width_and_height.diag src/blockdiag/tests/diagrams/non_rhombus_relation_height.diag src/blockdiag/tests/diagrams/outer_node_follows_node_in_group.diag src/blockdiag/tests/diagrams/plugin_attributes.diag src/blockdiag/tests/diagrams/plugin_autoclass.diag src/blockdiag/tests/diagrams/portrait_dots.diag src/blockdiag/tests/diagrams/quoted_node_id.diag src/blockdiag/tests/diagrams/reverse_multiple_groups.diag src/blockdiag/tests/diagrams/rhombus_relation_height.diag src/blockdiag/tests/diagrams/self_ref.diag src/blockdiag/tests/diagrams/separate1.diag src/blockdiag/tests/diagrams/separate2.diag src/blockdiag/tests/diagrams/simple_group.diag src/blockdiag/tests/diagrams/single_edge.diag src/blockdiag/tests/diagrams/single_node.diag src/blockdiag/tests/diagrams/skipped_circular.diag src/blockdiag/tests/diagrams/skipped_edge.diag src/blockdiag/tests/diagrams/skipped_edge_down.diag src/blockdiag/tests/diagrams/skipped_edge_flowchart_rightdown.diag src/blockdiag/tests/diagrams/skipped_edge_flowchart_rightdown2.diag src/blockdiag/tests/diagrams/skipped_edge_leftdown.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_down.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_flowchart_rightdown.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_flowchart_rightdown2.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_leftdown.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_right.diag src/blockdiag/tests/diagrams/skipped_edge_portrait_rightdown.diag src/blockdiag/tests/diagrams/skipped_edge_right.diag src/blockdiag/tests/diagrams/skipped_edge_rightdown.diag src/blockdiag/tests/diagrams/skipped_edge_rightup.diag src/blockdiag/tests/diagrams/skipped_edge_up.diag src/blockdiag/tests/diagrams/skipped_twin_circular.diag src/blockdiag/tests/diagrams/slided_children.diag src/blockdiag/tests/diagrams/stacked_node_and_edges.diag src/blockdiag/tests/diagrams/triple_branched.diag src/blockdiag/tests/diagrams/twin_circular_ref.diag src/blockdiag/tests/diagrams/twin_circular_ref_to_root.diag src/blockdiag/tests/diagrams/twin_forked.diag src/blockdiag/tests/diagrams/twin_multiple_parent_node.diag src/blockdiag/tests/diagrams/two_edges.diag src/blockdiag/tests/diagrams/white.gif src/blockdiag/tests/diagrams/errors/belongs_to_two_groups.diag src/blockdiag/tests/diagrams/errors/group_follows_node.diag src/blockdiag/tests/diagrams/errors/lexer_error.diag src/blockdiag/tests/diagrams/errors/node_follows_group.diag src/blockdiag/tests/diagrams/errors/unknown_diagram_default_shape.diag src/blockdiag/tests/diagrams/errors/unknown_diagram_edge_layout.diag src/blockdiag/tests/diagrams/errors/unknown_diagram_orientation.diag src/blockdiag/tests/diagrams/errors/unknown_diagram_type.diag src/blockdiag/tests/diagrams/errors/unknown_edge_class.diag src/blockdiag/tests/diagrams/errors/unknown_edge_dir.diag src/blockdiag/tests/diagrams/errors/unknown_edge_hstyle.diag src/blockdiag/tests/diagrams/errors/unknown_edge_style.diag src/blockdiag/tests/diagrams/errors/unknown_group_class.diag src/blockdiag/tests/diagrams/errors/unknown_group_orientation.diag src/blockdiag/tests/diagrams/errors/unknown_group_shape.diag src/blockdiag/tests/diagrams/errors/unknown_node_attribute.diag src/blockdiag/tests/diagrams/errors/unknown_node_class.diag src/blockdiag/tests/diagrams/errors/unknown_node_shape.diag src/blockdiag/tests/diagrams/errors/unknown_node_style.diag src/blockdiag/tests/diagrams/errors/unknown_plugin.diag src/blockdiag/tests/rst/__init__.py src/blockdiag/tests/rst/test_base_directives.py src/blockdiag/tests/rst/test_blockdiag_directives.py src/blockdiag/utils/__init__.py src/blockdiag/utils/bootstrap.py src/blockdiag/utils/compat.py src/blockdiag/utils/config.py src/blockdiag/utils/fontmap.py src/blockdiag/utils/images.py src/blockdiag/utils/logging.py src/blockdiag/utils/myitertools.py src/blockdiag/utils/urlutil.py src/blockdiag/utils/uuid.py src/blockdiag/utils/rst/__init__.py src/blockdiag/utils/rst/directives.py src/blockdiag/utils/rst/nodes.py�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag.egg-info/top_level.txt������������������������������������������������0000644�0000766�0000024�00000000041�12556430033�023166� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag blockdiag_sphinxhelper �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/src/blockdiag_sphinxhelper.py�������������������������������������������������������0000644�0000766�0000024�00000001641�12472051375�022121� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������# -*- coding: utf-8 -*- # Copyright 2011 Takeshi KOMIYA # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import blockdiag.parser import blockdiag.builder import blockdiag.drawer import blockdiag.utils.bootstrap import blockdiag.utils.compat import blockdiag.utils.fontmap import blockdiag.utils.rst.nodes import blockdiag.utils.rst.directives __all__ = [ 'core', 'utils' ] core = blockdiag utils = blockdiag.utils �����������������������������������������������������������������������������������������������blockdiag-1.5.3/test.sh�����������������������������������������������������������������������������0000755�0000766�0000024�00000000204�12534761142�015557� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/bin/sh bin/blockdiag --debug -Tsvg -o blockdiag.svg $1 $2 $3 $4 $5 #bin/blockdiag --debug -Tpng -o blockdiag.png $1 $2 $3 $4 $5 ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������blockdiag-1.5.3/tox.ini�����������������������������������������������������������������������������0000644�0000766�0000024�00000001030�12526267752�015563� 0����������������������������������������������������������������������������������������������������ustar �tkomiya�������������������������staff���������������������������0000000�0000000������������������������������������������������������������������������������������������������������������������������������������������������������������������������[tox] envlist=py26,py27,py32,py33,py34,pillow2.0,pillow2.2,pillow2.4 [testenv] deps= nose mock flake8 flake8-coding docutils reportlab wand passenv= ALL_TESTS commands= nosetests flake8 src [testenv:py26] deps= nose mock flake8 docutils unittest2 reportlab wand [testenv:pillow2.0] deps= {[testenv]deps} Pillow<=2.0.9999 [testenv:pillow2.2] deps= {[testenv]deps} Pillow<=2.2.9999 [testenv:pillow2.4] deps= {[testenv]deps} Pillow<=2.4.9999 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������