doc/hgmanpage.py
author Pierre-Yves David <pierre-yves.david@octobus.net>
Sat, 25 Apr 2020 12:37:46 +0200
changeset 46151 c6bc77f7e593
parent 45957 89a2afe31e82
child 47031 f38bf44e077f
permissions -rw-r--r--
copies-rust: tokenize all paths into integer Copy information for each changesets tend to affect a small new number of path. However, each of these path might be handled a large number of time. Handling HgPathBuf (aka `Vec<u8>`) is expensive. Handling integer is cheap. With this patch we: - turn any input path into an integer "token" early, - do all the internal logic using such "token", - turn "token" back into path right before returning a result. This gives use a quite significant performance boost in our slower cases. Repo Case Source-Rev Dest-Rev # of revisions old time new time Difference Factor time per rev --------------------------------------------------------------------------------------------------------------------------------------------------------------- pypy x000_revs_x000_added_x_copies 8a3b5bfd266e 2c68e87c3efe : 6780 revs, 0.092828 s, 0.081225 s, -0.011603 s, × 0.8750, 11 µs/rev pypy x0000_revs_x_added_0_copies d1defd0dc478 c9cb1334cc78 : 43645 revs, 0.711975 s, 0.586011 s, -0.125964 s, × 0.8231, 13 µs/rev pypy x0000_revs_xx000_added_x000_copies 08ea3258278e d9fa043f30c0 : 11316 revs, 0.124505 s, 0.114173 s, -0.010332 s, × 0.9170, 10 µs/rev netbeans x000_revs_x000_added_x000_copies ff453e9fee32 411350406ec2 : 5750 revs, 0.072072 s, 0.061004 s, -0.011068 s, × 0.8464, 10 µs/rev netbeans x0000_revs_xx000_added_x000_copies 588c2d1ced70 1aad62e59ddd : 66949 revs, 0.682732 s, 0.535874 s, -0.146858 s, × 0.7849, 8 µs/rev mozilla-central x00000_revs_x0000_added_x0000_copies 6832ae71433c 4c222a1d9a00 : 153721 revs, 1.935918 s, 1.781383 s, -0.154535 s, × 0.9202, 11 µs/rev mozilla-central x00000_revs_x00000_added_x000_copies 76caed42cf7c 1daa622bbe42 : 204976 revs, 2.827320 s, 2.603867 s, -0.223453 s, × 0.9210, 12 µs/rev mozilla-try x0000_revs_xx000_added_x000_copies 89294cd501d9 7ccb2fc7ccb5 : 97052 revs, 3.243010 s, 1.529120 s, -1.713890 s, × 0.4715, 15 µs/rev mozilla-try x00000_revs_x_added_0_copies 6a320851d377 1ebb79acd503 : 363753 revs, 5.693818 s, 4.842699 s, -0.851119 s, × 0.8505, 13 µs/rev mozilla-try x00000_revs_x_added_x_copies 5173c4b6f97c 95d83ee7242d : 362229 revs, 5.677655 s, 4.761732 s, -0.915923 s, × 0.8387, 13 µs/rev mozilla-try x00000_revs_x000_added_x_copies 9126823d0e9c ca82787bb23c : 359344 revs, 5.563370 s, 4.733912 s, -0.829458 s, × 0.8509, 13 µs/rev mozilla-try x00000_revs_x0000_added_x0000_copies 8d3fafa80d4b eb884023b810 : 192665 revs, 2.864099 s, 2.593410 s, -0.270689 s, × 0.9055, 13 µs/rev mozilla-try x00000_revs_x00000_added_x0000_copies 1b661134e2ca 1ae03d022d6d : 228985 revs, 113.297287 s, 41.041198 s, -72.256089 s, × 0.3622, 179 µs/rev mozilla-try x00000_revs_x00000_added_x000_copies 9b2a99adc05e 8e29777b48e6 : 382065 revs, 59.498652 s, 27.915689 s, -31.582963 s, × 0.4692, 73 µs/rev Full timing comparison between this revision and the previous one: Repo Case Source-Rev Dest-Rev # of revisions old time new time Difference Factor time per rev --------------------------------------------------------------------------------------------------------------------------------------------------------------- mercurial x_revs_x_added_0_copies ad6b123de1c7 39cfcef4f463 : 1 revs, 0.000042 s, 0.000042 s, +0.000000 s, × 1.0000, 42 µs/rev mercurial x_revs_x_added_x_copies 2b1c78674230 0c1d10351869 : 6 revs, 0.000104 s, 0.000110 s, +0.000006 s, × 1.0577, 18 µs/rev mercurial x000_revs_x000_added_x_copies 81f8ff2a9bf2 dd3267698d84 : 1032 revs, 0.004913 s, 0.004918 s, +0.000005 s, × 1.0010, 4 µs/rev pypy x_revs_x_added_0_copies aed021ee8ae8 099ed31b181b : 9 revs, 0.000191 s, 0.000195 s, +0.000004 s, × 1.0209, 21 µs/rev pypy x_revs_x000_added_0_copies 4aa4e1f8e19a 359343b9ac0e : 1 revs, 0.000050 s, 0.000049 s, -0.000001 s, × 0.9800, 49 µs/rev pypy x_revs_x_added_x_copies ac52eb7bbbb0 72e022663155 : 7 revs, 0.000112 s, 0.000112 s, +0.000000 s, × 1.0000, 16 µs/rev pypy x_revs_x00_added_x_copies c3b14617fbd7 ace7255d9a26 : 1 revs, 0.000288 s, 0.000324 s, +0.000036 s, × 1.1250, 324 µs/rev pypy x_revs_x000_added_x000_copies df6f7a526b60 a83dc6a2d56f : 6 revs, 0.010411 s, 0.010611 s, +0.000200 s, × 1.0192, 1768 µs/rev pypy x000_revs_xx00_added_0_copies 89a76aede314 2f22446ff07e : 4785 revs, 0.052852 s, 0.050835 s, -0.002017 s, × 0.9618, 10 µs/rev pypy x000_revs_x000_added_x_copies 8a3b5bfd266e 2c68e87c3efe : 6780 revs, 0.092828 s, 0.081225 s, -0.011603 s, × 0.8750, 11 µs/rev pypy x000_revs_x000_added_x000_copies 89a76aede314 7b3dda341c84 : 5441 revs, 0.063269 s, 0.061291 s, -0.001978 s, × 0.9687, 11 µs/rev pypy x0000_revs_x_added_0_copies d1defd0dc478 c9cb1334cc78 : 43645 revs, 0.711975 s, 0.586011 s, -0.125964 s, × 0.8231, 13 µs/rev pypy x0000_revs_xx000_added_0_copies bf2c629d0071 4ffed77c095c : 2 revs, 0.012771 s, 0.012824 s, +0.000053 s, × 1.0042, 6412 µs/rev pypy x0000_revs_xx000_added_x000_copies 08ea3258278e d9fa043f30c0 : 11316 revs, 0.124505 s, 0.114173 s, -0.010332 s, × 0.9170, 10 µs/rev netbeans x_revs_x_added_0_copies fb0955ffcbcd a01e9239f9e7 : 2 revs, 0.000082 s, 0.000085 s, +0.000003 s, × 1.0366, 42 µs/rev netbeans x_revs_x000_added_0_copies 6f360122949f 20eb231cc7d0 : 2 revs, 0.000111 s, 0.000108 s, -0.000003 s, × 0.9730, 54 µs/rev netbeans x_revs_x_added_x_copies 1ada3faf6fb6 5a39d12eecf4 : 3 revs, 0.000171 s, 0.000175 s, +0.000004 s, × 1.0234, 58 µs/rev netbeans x_revs_x00_added_x_copies 35be93ba1e2c 9eec5e90c05f : 9 revs, 0.000708 s, 0.000719 s, +0.000011 s, × 1.0155, 79 µs/rev netbeans x000_revs_xx00_added_0_copies eac3045b4fdd 51d4ae7f1290 : 1421 revs, 0.010608 s, 0.010175 s, -0.000433 s, × 0.9592, 7 µs/rev netbeans x000_revs_x000_added_x_copies e2063d266acd 6081d72689dc : 1533 revs, 0.015635 s, 0.015569 s, -0.000066 s, × 0.9958, 10 µs/rev netbeans x000_revs_x000_added_x000_copies ff453e9fee32 411350406ec2 : 5750 revs, 0.072072 s, 0.061004 s, -0.011068 s, × 0.8464, 10 µs/rev netbeans x0000_revs_xx000_added_x000_copies 588c2d1ced70 1aad62e59ddd : 66949 revs, 0.682732 s, 0.535874 s, -0.146858 s, × 0.7849, 8 µs/rev mozilla-central x_revs_x_added_0_copies 3697f962bb7b 7015fcdd43a2 : 2 revs, 0.000090 s, 0.000090 s, +0.000000 s, × 1.0000, 45 µs/rev mozilla-central x_revs_x000_added_0_copies dd390860c6c9 40d0c5bed75d : 8 revs, 0.000210 s, 0.000281 s, +0.000071 s, × 1.3381, 35 µs/rev mozilla-central x_revs_x_added_x_copies 8d198483ae3b 14207ffc2b2f : 9 revs, 0.000182 s, 0.000187 s, +0.000005 s, × 1.0275, 20 µs/rev mozilla-central x_revs_x00_added_x_copies 98cbc58cc6bc 446a150332c3 : 7 revs, 0.000594 s, 0.000660 s, +0.000066 s, × 1.1111, 94 µs/rev mozilla-central x_revs_x000_added_x000_copies 3c684b4b8f68 0a5e72d1b479 : 3 revs, 0.003102 s, 0.003385 s, +0.000283 s, × 1.0912, 1128 µs/rev mozilla-central x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 6 revs, 0.060234 s, 0.069812 s, +0.009578 s, × 1.1590, 11635 µs/rev mozilla-central x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 1593 revs, 0.006300 s, 0.006503 s, +0.000203 s, × 1.0322, 4 µs/rev mozilla-central x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 41 revs, 0.004817 s, 0.004988 s, +0.000171 s, × 1.0355, 121 µs/rev mozilla-central x000_revs_x000_added_x000_copies 7c97034feb78 4407bd0c6330 : 7839 revs, 0.065451 s, 0.063963 s, -0.001488 s, × 0.9773, 8 µs/rev mozilla-central x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 615 revs, 0.026282 s, 0.026225 s, -0.000057 s, × 0.9978, 42 µs/rev mozilla-central x0000_revs_xx000_added_x000_copies f78c615a656c 96a38b690156 : 30263 revs, 0.206873 s, 0.201377 s, -0.005496 s, × 0.9734, 6 µs/rev mozilla-central x00000_revs_x0000_added_x0000_copies 6832ae71433c 4c222a1d9a00 : 153721 revs, 1.935918 s, 1.781383 s, -0.154535 s, × 0.9202, 11 µs/rev mozilla-central x00000_revs_x00000_added_x000_copies 76caed42cf7c 1daa622bbe42 : 204976 revs, 2.827320 s, 2.603867 s, -0.223453 s, × 0.9210, 12 µs/rev mozilla-try x_revs_x_added_0_copies aaf6dde0deb8 9790f499805a : 2 revs, 0.000842 s, 0.000845 s, +0.000003 s, × 1.0036, 422 µs/rev mozilla-try x_revs_x000_added_0_copies d8d0222927b4 5bb8ce8c7450 : 2 revs, 0.000870 s, 0.000862 s, -0.000008 s, × 0.9908, 431 µs/rev mozilla-try x_revs_x_added_x_copies 092fcca11bdb 936255a0384a : 4 revs, 0.000165 s, 0.000161 s, -0.000004 s, × 0.9758, 40 µs/rev mozilla-try x_revs_x00_added_x_copies b53d2fadbdb5 017afae788ec : 2 revs, 0.001145 s, 0.001163 s, +0.000018 s, × 1.0157, 581 µs/rev mozilla-try x_revs_x000_added_x000_copies 20408ad61ce5 6f0ee96e21ad : 1 revs, 0.026500 s, 0.032414 s, +0.005914 s, × 1.2232, 32414 µs/rev mozilla-try x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 6 revs, 0.059407 s, 0.070149 s, +0.010742 s, × 1.1808, 11691 µs/rev mozilla-try x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 1593 revs, 0.006325 s, 0.006526 s, +0.000201 s, × 1.0318, 4 µs/rev mozilla-try x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 41 revs, 0.005171 s, 0.005187 s, +0.000016 s, × 1.0031, 126 µs/rev mozilla-try x000_revs_x000_added_x000_copies 1346fd0130e4 4c65cbdabc1f : 6657 revs, 0.066837 s, 0.065047 s, -0.001790 s, × 0.9732, 9 µs/rev mozilla-try x0000_revs_x_added_0_copies 63519bfd42ee a36a2a865d92 : 40314 revs, 0.314252 s, 0.301129 s, -0.013123 s, × 0.9582, 7 µs/rev mozilla-try x0000_revs_x_added_x_copies 9fe69ff0762d bcabf2a78927 : 38690 revs, 0.304160 s, 0.280683 s, -0.023477 s, × 0.9228, 7 µs/rev mozilla-try x0000_revs_xx000_added_x_copies 156f6e2674f2 4d0f2c178e66 : 8598 revs, 0.089223 s, 0.084897 s, -0.004326 s, × 0.9515, 9 µs/rev mozilla-try x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 615 revs, 0.026711 s, 0.026620 s, -0.000091 s, × 0.9966, 43 µs/rev mozilla-try x0000_revs_xx000_added_x000_copies 89294cd501d9 7ccb2fc7ccb5 : 97052 revs, 3.243010 s, 1.529120 s, -1.713890 s, × 0.4715, 15 µs/rev mozilla-try x0000_revs_x0000_added_x0000_copies e928c65095ed e951f4ad123a : 52031 revs, 0.756500 s, 0.738709 s, -0.017791 s, × 0.9765, 14 µs/rev mozilla-try x00000_revs_x_added_0_copies 6a320851d377 1ebb79acd503 : 363753 revs, 5.693818 s, 4.842699 s, -0.851119 s, × 0.8505, 13 µs/rev mozilla-try x00000_revs_x00000_added_0_copies dc8a3ca7010e d16fde900c9c : 34414 revs, 0.590904 s, 0.596946 s, +0.006042 s, × 1.0102, 17 µs/rev mozilla-try x00000_revs_x_added_x_copies 5173c4b6f97c 95d83ee7242d : 362229 revs, 5.677655 s, 4.761732 s, -0.915923 s, × 0.8387, 13 µs/rev mozilla-try x00000_revs_x000_added_x_copies 9126823d0e9c ca82787bb23c : 359344 revs, 5.563370 s, 4.733912 s, -0.829458 s, × 0.8509, 13 µs/rev mozilla-try x00000_revs_x0000_added_x0000_copies 8d3fafa80d4b eb884023b810 : 192665 revs, 2.864099 s, 2.593410 s, -0.270689 s, × 0.9055, 13 µs/rev mozilla-try x00000_revs_x00000_added_x0000_copies 1b661134e2ca 1ae03d022d6d : 228985 revs, 113.297287 s, 41.041198 s, -72.256089 s, × 0.3622, 179 µs/rev mozilla-try x00000_revs_x00000_added_x000_copies 9b2a99adc05e 8e29777b48e6 : 382065 revs, 59.498652 s, 27.915689 s, -31.582963 s, × 0.4692, 73 µs/rev Full timing comparison between this revision and the filelog copy tracing. Repo Case Source-Rev Dest-Rev # of revisions filelog sidedata Difference Factor time per rev --------------------------------------------------------------------------------------------------------------------------------------------------------------- mercurial x_revs_x_added_0_copies ad6b123de1c7 39cfcef4f463 : 1 revs, 0.000903 s, 0.000042 s, -0.000861 s, × 0.0465, 41 µs/rev mercurial x_revs_x_added_x_copies 2b1c78674230 0c1d10351869 : 6 revs, 0.001861 s, 0.000110 s, -0.001751 s, × 0.0591, 18 µs/rev mercurial x000_revs_x000_added_x_copies 81f8ff2a9bf2 dd3267698d84 : 1032 revs, 0.018577 s, 0.004918 s, -0.013659 s, × 0.2647, 4 µs/rev pypy x_revs_x_added_0_copies aed021ee8ae8 099ed31b181b : 9 revs, 0.001519 s, 0.000195 s, -0.001324 s, × 0.1283, 21 µs/rev pypy x_revs_x000_added_0_copies 4aa4e1f8e19a 359343b9ac0e : 1 revs, 0.213855 s, 0.000049 s, -0.350d73 s, × 0.0002, 48 µs/rev pypy x_revs_x_added_x_copies ac52eb7bbbb0 72e022663155 : 7 revs, 0.017022 s, 0.000112 s, -0.016910 s, × 0.0065, 15 µs/rev pypy x_revs_x00_added_x_copies c3b14617fbd7 ace7255d9a26 : 1 revs, 0.019398 s, 0.000324 s, -0.019074 s, × 0.0167, 323 µs/rev pypy x_revs_x000_added_x000_copies df6f7a526b60 a83dc6a2d56f : 6 revs, 0.769467 s, 0.010611 s, -0.758856 s, × 0.0137, 1768 µs/rev pypy x000_revs_xx00_added_0_copies 89a76aede314 2f22446ff07e : 4785 revs, 1.221952 s, 0.050835 s, -1.171117 s, × 0.0416, 10 µs/rev pypy x000_revs_x000_added_x_copies 8a3b5bfd266e 2c68e87c3efe : 6780 revs, 1.304007 s, 0.081225 s, -1.222782 s, × 0.0622, 11 µs/rev pypy x000_revs_x000_added_x000_copies 89a76aede314 7b3dda341c84 : 5441 revs, 1.686610 s, 0.061291 s, -1.625319 s, × 0.0363, 11 µs/rev pypy x0000_revs_x_added_0_copies d1defd0dc478 c9cb1334cc78 : 43645 revs, 0.001107 s, 0.586011 s, +0.584904 s, × 529.36, 13 µs/rev pypy x0000_revs_xx000_added_0_copies bf2c629d0071 4ffed77c095c : 2 revs, 1.100760 s, 0.012824 s, -1.087936 s, × 0.0116, 6408 µs/rev pypy x0000_revs_xx000_added_x000_copies 08ea3258278e d9fa043f30c0 : 11316 revs, 1.350547 s, 0.114173 s, -1.236374 s, × 0.0845, 10 µs/rev netbeans x_revs_x_added_0_copies fb0955ffcbcd a01e9239f9e7 : 2 revs, 0.027864 s, 0.000085 s, -0.027779 s, × 0.0030, 42 µs/rev netbeans x_revs_x000_added_0_copies 6f360122949f 20eb231cc7d0 : 2 revs, 0.132479 s, 0.000108 s, -0.132371 s, × 0.0008, 53 µs/rev netbeans x_revs_x_added_x_copies 1ada3faf6fb6 5a39d12eecf4 : 3 revs, 0.025405 s, 0.000175 s, -0.025230 s, × 0.0068, 58 µs/rev netbeans x_revs_x00_added_x_copies 35be93ba1e2c 9eec5e90c05f : 9 revs, 0.053244 s, 0.000719 s, -0.052525 s, × 0.0135, 79 µs/rev netbeans x000_revs_xx00_added_0_copies eac3045b4fdd 51d4ae7f1290 : 1421 revs, 0.038017 s, 0.010175 s, -0.027842 s, × 0.2676, 7 µs/rev netbeans x000_revs_x000_added_x_copies e2063d266acd 6081d72689dc : 1533 revs, 0.198308 s, 0.015569 s, -0.182739 s, × 0.0785, 10 µs/rev netbeans x000_revs_x000_added_x000_copies ff453e9fee32 411350406ec2 : 5750 revs, 0.949749 s, 0.061004 s, -0.888745 s, × 0.0642, 10 µs/rev netbeans x0000_revs_xx000_added_x000_copies 588c2d1ced70 1aad62e59ddd : 66949 revs, 3.932262 s, 0.535874 s, -3.396388 s, × 0.1362, 8 µs/rev mozilla-central x_revs_x_added_0_copies 3697f962bb7b 7015fcdd43a2 : 2 revs, 0.024490 s, 0.000090 s, -0.024400 s, × 0.0036, 44 µs/rev mozilla-central x_revs_x000_added_0_copies dd390860c6c9 40d0c5bed75d : 8 revs, 0.143885 s, 0.000281 s, -0.143604 s, × 0.0019, 35 µs/rev mozilla-central x_revs_x_added_x_copies 8d198483ae3b 14207ffc2b2f : 9 revs, 0.025471 s, 0.000187 s, -0.025284 s, × 0.0073, 20 µs/rev mozilla-central x_revs_x00_added_x_copies 98cbc58cc6bc 446a150332c3 : 7 revs, 0.086013 s, 0.000660 s, -0.085353 s, × 0.0076, 94 µs/rev mozilla-central x_revs_x000_added_x000_copies 3c684b4b8f68 0a5e72d1b479 : 3 revs, 0.200726 s, 0.003385 s, -0.197341 s, × 0.0168, 1127 µs/rev mozilla-central x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 6 revs, 2.224171 s, 0.069812 s, -2.154359 s, × 0.0313, 11633 µs/rev mozilla-central x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 1593 revs, 0.090780 s, 0.006503 s, -0.084277 s, × 0.0716, 4 µs/rev mozilla-central x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 41 revs, 0.764805 s, 0.004988 s, -0.759817 s, × 0.0065, 121 µs/rev mozilla-central x000_revs_x000_added_x000_copies 7c97034feb78 4407bd0c6330 : 7839 revs, 1.161405 s, 0.063963 s, -1.097442 s, × 0.0550, 8 µs/rev mozilla-central x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 615 revs, 6.816186 s, 0.026225 s, -6.789961 s, × 0.0038, 42 µs/rev mozilla-central x0000_revs_xx000_added_x000_copies f78c615a656c 96a38b690156 : 30263 revs, 3.374819 s, 0.201377 s, -3.173442 s, × 0.0596, 6 µs/rev mozilla-central x00000_revs_x0000_added_x0000_copies 6832ae71433c 4c222a1d9a00 : 153721 revs, 16.285469 s, 1.781383 s, -14.504086 s, × 0.1093, 11 µs/rev mozilla-central x00000_revs_x00000_added_x000_copies 76caed42cf7c 1daa622bbe42 : 204976 revs, 21.207733 s, 2.603867 s, -18.603866 s, × 0.1227, 12 µs/rev mozilla-try x_revs_x_added_0_copies aaf6dde0deb8 9790f499805a : 2 revs, 0.080843 s, 0.000845 s, -0.079998 s, × 0.0104, 422 µs/rev mozilla-try x_revs_x000_added_0_copies d8d0222927b4 5bb8ce8c7450 : 2 revs, 0.511068 s, 0.000862 s, -0.510206 s, × 0.0016, 430 µs/rev mozilla-try x_revs_x_added_x_copies 092fcca11bdb 936255a0384a : 4 revs, 0.021573 s, 0.000161 s, -0.021412 s, × 0.0074, 40 µs/rev mozilla-try x_revs_x00_added_x_copies b53d2fadbdb5 017afae788ec : 2 revs, 0.227726 s, 0.001163 s, -0.226563 s, × 0.0051, 581 µs/rev mozilla-try x_revs_x000_added_x000_copies 20408ad61ce5 6f0ee96e21ad : 1 revs, 1.120448 s, 0.032414 s, -1.088034 s, × 0.0289, 32381 µs/rev mozilla-try x_revs_x0000_added_x0000_copies effb563bb7e5 c07a39dc4e80 : 6 revs, 2.241713 s, 0.070149 s, -2.171564 s, × 0.0312, 11689 µs/rev mozilla-try x000_revs_xx00_added_0_copies 6100d773079a 04a55431795e : 1593 revs, 0.090633 s, 0.006526 s, -0.084107 s, × 0.0720, 4 µs/rev mozilla-try x000_revs_x000_added_x_copies 9f17a6fc04f9 2d37b966abed : 41 revs, 0.770403 s, 0.005187 s, -0.765216 s, × 0.0067, 126 µs/rev mozilla-try x000_revs_x000_added_x000_copies 1346fd0130e4 4c65cbdabc1f : 6657 revs, 1.184557 s, 0.065047 s, -1.119510 s, × 0.0549, 9 µs/rev mozilla-try x0000_revs_x_added_0_copies 63519bfd42ee a36a2a865d92 : 40314 revs, 0.085790 s, 0.301129 s, +0.215339 s, × 3.5100, 7 µs/rev mozilla-try x0000_revs_x_added_x_copies 9fe69ff0762d bcabf2a78927 : 38690 revs, 0.080616 s, 0.280683 s, +0.200067 s, × 3.4817, 7 µs/rev mozilla-try x0000_revs_xx000_added_x_copies 156f6e2674f2 4d0f2c178e66 : 8598 revs, 7.712554 s, 0.084897 s, -7.627657 s, × 0.0110, 9 µs/rev mozilla-try x0000_revs_xx000_added_0_copies 9eec5917337d 67118cc6dcad : 615 revs, 6.937294 s, 0.026620 s, -6.910674 s, × 0.0038, 43 µs/rev mozilla-try x0000_revs_xx000_added_x000_copies 89294cd501d9 7ccb2fc7ccb5 : 97052 revs, 7.712313 s, 1.529120 s, -6.183193 s, × 0.1982, 15 µs/rev mozilla-try x0000_revs_x0000_added_x0000_copies e928c65095ed e951f4ad123a : 52031 revs, 9.966910 s, 0.738709 s, -9.228201 s, × 0.0741, 14 µs/rev mozilla-try x00000_revs_x_added_0_copies 6a320851d377 1ebb79acd503 : 363753 revs, 0.090397 s, 4.842699 s, +4.752302 s, × 53.571, 13 µs/rev mozilla-try x00000_revs_x00000_added_0_copies dc8a3ca7010e d16fde900c9c : 34414 revs, 27.817167 s, 0.596946 s, -27.220221 s, × 0.0214, 17 µs/rev mozilla-try x00000_revs_x_added_x_copies 5173c4b6f97c 95d83ee7242d : 362229 revs, 0.091305 s, 4.761732 s, +4.670427 s, × 52.151, 13 µs/rev mozilla-try x00000_revs_x000_added_x_copies 9126823d0e9c ca82787bb23c : 359344 revs, 0.231183 s, 4.733912 s, +4.502729 s, × 20.476, 13 µs/rev mozilla-try x00000_revs_x0000_added_x0000_copies 8d3fafa80d4b eb884023b810 : 192665 revs, 19.830617 s, 2.593410 s, -17.237207 s, × 0.1307, 13 µs/rev mozilla-try x00000_revs_x00000_added_x0000_copies 1b661134e2ca 1ae03d022d6d : 228985 revs, 21.743873 s, 41.041198 s, +19.297325 s, × 1.8874, 179 µs/rev mozilla-try x00000_revs_x00000_added_x000_copies 9b2a99adc05e 8e29777b48e6 : 382065 revs, 25.935037 s, 27.915689 s, +1.980652 s, × 1.0763, 73 µs/rev Differential Revision: https://phab.mercurial-scm.org/D9493

# -*- coding: utf-8 -*-
# $Id: manpage.py 6110 2009-08-31 14:40:33Z grubert $
# Author: Engelbert Gruber <grubert@users.sourceforge.net>
# Copyright: This module is put into the public domain.

"""
Simple man page writer for reStructuredText.

Man pages (short for "manual pages") contain system documentation on unix-like
systems. The pages are grouped in numbered sections:

 1 executable programs and shell commands
 2 system calls
 3 library functions
 4 special files
 5 file formats
 6 games
 7 miscellaneous
 8 system administration

Man pages are written in *troff*, a text file formatting system.

See http://www.tldp.org/HOWTO/Man-Page for a start.

Man pages have no subsections only parts.
Standard parts

  NAME ,
  SYNOPSIS ,
  DESCRIPTION ,
  OPTIONS ,
  FILES ,
  SEE ALSO ,
  BUGS ,

and

  AUTHOR .

A unix-like system keeps an index of the DESCRIPTIONs, which is accesable
by the command whatis or apropos.

"""
from __future__ import absolute_import

__docformat__ = 'reStructuredText'

import inspect
import re

from docutils import (
    languages,
    nodes,
    writers,
)

try:
    import roman
except ImportError:
    from docutils.utils import roman

FIELD_LIST_INDENT = 7
DEFINITION_LIST_INDENT = 7
OPTION_LIST_INDENT = 7
BLOCKQOUTE_INDENT = 3.5

# Define two macros so man/roff can calculate the
# indent/unindent margins by itself
MACRO_DEF = r""".
.nr rst2man-indent-level 0
.
.de1 rstReportMargin
\\$1 \\n[an-margin]
level \\n[rst2man-indent-level]
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
-
\\n[rst2man-indent0]
\\n[rst2man-indent1]
\\n[rst2man-indent2]
..
.de1 INDENT
.\" .rstReportMargin pre:
. RS \\$1
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
. nr rst2man-indent-level +1
.\" .rstReportMargin post:
..
.de UNINDENT
. RE
.\" indent \\n[an-margin]
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
.nr rst2man-indent-level -1
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
"""


class Writer(writers.Writer):

    supported = 'manpage'
    """Formats this writer supports."""

    output = None
    """Final translated form of `document`."""

    def __init__(self):
        writers.Writer.__init__(self)
        self.translator_class = Translator

    def translate(self):
        visitor = self.translator_class(self.document)
        self.document.walkabout(visitor)
        self.output = visitor.astext()


class Table(object):
    def __init__(self):
        self._rows = []
        self._options = ['center']
        self._tab_char = '\t'
        self._coldefs = []

    def new_row(self):
        self._rows.append([])

    def append_separator(self, separator):
        """Append the separator for table head."""
        self._rows.append([separator])

    def append_cell(self, cell_lines):
        """cell_lines is an array of lines"""
        start = 0
        if len(cell_lines) > 0 and cell_lines[0] == '.sp\n':
            start = 1
        self._rows[-1].append(cell_lines[start:])
        if len(self._coldefs) < len(self._rows[-1]):
            self._coldefs.append('l')

    def _minimize_cell(self, cell_lines):
        """Remove leading and trailing blank and ``.sp`` lines"""
        while cell_lines and cell_lines[0] in ('\n', '.sp\n'):
            del cell_lines[0]
        while cell_lines and cell_lines[-1] in ('\n', '.sp\n'):
            del cell_lines[-1]

    def as_list(self):
        text = ['.TS\n']
        text.append(' '.join(self._options) + ';\n')
        text.append('|%s|.\n' % ('|'.join(self._coldefs)))
        for row in self._rows:
            # row = array of cells. cell = array of lines.
            text.append('_\n')  # line above
            text.append('T{\n')
            for i in range(len(row)):
                cell = row[i]
                self._minimize_cell(cell)
                text.extend(cell)
                if not text[-1].endswith('\n'):
                    text[-1] += '\n'
                if i < len(row) - 1:
                    text.append('T}' + self._tab_char + 'T{\n')
                else:
                    text.append('T}\n')
        text.append('_\n')
        text.append('.TE\n')
        return text


class Translator(nodes.NodeVisitor):
    """"""

    words_and_spaces = re.compile(r'\S+| +|\n')
    document_start = """Man page generated from reStructuredText."""

    def __init__(self, document):
        nodes.NodeVisitor.__init__(self, document)
        self.settings = settings = document.settings
        lcode = settings.language_code
        arglen = len(inspect.getargspec(languages.get_language)[0])
        if arglen == 2:
            self.language = languages.get_language(
                lcode, self.document.reporter
            )
        else:
            self.language = languages.get_language(lcode)
        self.head = []
        self.body = []
        self.foot = []
        self.section_level = 0
        self.context = []
        self.topic_class = ''
        self.colspecs = []
        self.compact_p = 1
        self.compact_simple = None
        # the list style "*" bullet or "#" numbered
        self._list_char = []
        # writing the header .TH and .SH NAME is postboned after
        # docinfo.
        self._docinfo = {
            "title": "",
            "title_upper": "",
            "subtitle": "",
            "manual_section": "",
            "manual_group": "",
            "author": [],
            "date": "",
            "copyright": "",
            "version": "",
        }
        self._docinfo_keys = []  # a list to keep the sequence as in source.
        self._docinfo_names = {}  # to get name from text not normalized.
        self._in_docinfo = None
        self._active_table = None
        self._in_literal = False
        self.header_written = 0
        self._line_block = 0
        self.authors = []
        self.section_level = 0
        self._indent = [0]
        # central definition of simple processing rules
        # what to output on : visit, depart
        # Do not use paragraph requests ``.PP`` because these set indentation.
        # use ``.sp``. Remove superfluous ``.sp`` in ``astext``.
        #
        # Fonts are put on a stack, the top one is used.
        # ``.ft P`` or ``\\fP`` pop from stack.
        # ``B`` bold, ``I`` italic, ``R`` roman should be available.
        # Hopefully ``C`` courier too.
        self.defs = {
            'indent': ('.INDENT %.1f\n', '.UNINDENT\n'),
            'definition_list_item': ('.TP', ''),
            'field_name': ('.TP\n.B ', '\n'),
            'literal': ('\\fB', '\\fP'),
            'literal_block': ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'),
            'option_list_item': ('.TP\n', ''),
            'reference': (r'\%', r'\:'),
            'emphasis': ('\\fI', '\\fP'),
            'strong': ('\\fB', '\\fP'),
            'term': ('\n.B ', '\n'),
            'title_reference': ('\\fI', '\\fP'),
            'topic-title': ('.SS ',),
            'sidebar-title': ('.SS ',),
            'problematic': ('\n.nf\n', '\n.fi\n'),
        }
        # NOTE don't specify the newline before a dot-command, but ensure
        # it is there.

    def comment_begin(self, text):
        """Return commented version of the passed text WITHOUT end of
        line/comment."""
        prefix = '.\\" '
        out_text = ''.join(
            [(prefix + in_line + '\n') for in_line in text.split('\n')]
        )
        return out_text

    def comment(self, text):
        """Return commented version of the passed text."""
        return self.comment_begin(text) + '.\n'

    def ensure_eol(self):
        """Ensure the last line in body is terminated by new line."""
        if self.body[-1][-1] != '\n':
            self.body.append('\n')

    def astext(self):
        """Return the final formatted document as a string."""
        if not self.header_written:
            # ensure we get a ".TH" as viewers require it.
            self.head.append(self.header())
        # filter body
        for i in range(len(self.body) - 1, 0, -1):
            # remove superfluous vertical gaps.
            if self.body[i] == '.sp\n':
                if self.body[i - 1][:4] in ('.BI ', '.IP '):
                    self.body[i] = '.\n'
                elif (
                    self.body[i - 1][:3] == '.B '
                    and self.body[i - 2][:4] == '.TP\n'
                ):
                    self.body[i] = '.\n'
                elif (
                    self.body[i - 1] == '\n'
                    and self.body[i - 2][0] != '.'
                    and (
                        self.body[i - 3][:7] == '.TP\n.B '
                        or self.body[i - 3][:4] == '\n.B '
                    )
                ):
                    self.body[i] = '.\n'
        return ''.join(self.head + self.body + self.foot)

    def deunicode(self, text):
        text = text.replace(u'\xa0', '\\ ')
        text = text.replace(u'\u2020', '\\(dg')
        return text

    def visit_Text(self, node):
        text = node.astext()
        text = text.replace('\\', '\\e')
        replace_pairs = [
            (u'-', u'\\-'),
            (u"'", u'\\(aq'),
            (u'´', u"\\'"),
            (u'`', u'\\(ga'),
        ]
        for (in_char, out_markup) in replace_pairs:
            text = text.replace(in_char, out_markup)
        # unicode
        text = self.deunicode(text)
        if self._in_literal:
            # prevent interpretation of "." at line start
            if text[0] == '.':
                text = '\\&' + text
            text = text.replace('\n.', '\n\\&.')
        self.body.append(text)

    def depart_Text(self, node):
        pass

    def list_start(self, node):
        class enum_char(object):
            enum_style = {
                'bullet': '\\(bu',
                'emdash': '\\(em',
            }

            def __init__(self, style):
                self._style = style
                if 'start' in node:
                    self._cnt = node['start'] - 1
                else:
                    self._cnt = 0
                self._indent = 2
                if style == 'arabic':
                    # indentation depends on number of children
                    # and start value.
                    self._indent = len(str(len(node.children)))
                    self._indent += len(str(self._cnt)) + 1
                elif style == 'loweralpha':
                    self._cnt += ord('a') - 1
                    self._indent = 3
                elif style == 'upperalpha':
                    self._cnt += ord('A') - 1
                    self._indent = 3
                elif style.endswith('roman'):
                    self._indent = 5

            def __next__(self):
                if self._style == 'bullet':
                    return self.enum_style[self._style]
                elif self._style == 'emdash':
                    return self.enum_style[self._style]
                self._cnt += 1
                # TODO add prefix postfix
                if self._style == 'arabic':
                    return "%d." % self._cnt
                elif self._style in ('loweralpha', 'upperalpha'):
                    return "%c." % self._cnt
                elif self._style.endswith('roman'):
                    res = roman.toRoman(self._cnt) + '.'
                    if self._style.startswith('upper'):
                        return res.upper()
                    return res.lower()
                else:
                    return "%d." % self._cnt

            next = __next__

            def get_width(self):
                return self._indent

            def __repr__(self):
                return 'enum_style-%s' % list(self._style)

        if 'enumtype' in node:
            self._list_char.append(enum_char(node['enumtype']))
        else:
            self._list_char.append(enum_char('bullet'))
        if len(self._list_char) > 1:
            # indent nested lists
            self.indent(self._list_char[-2].get_width())
        else:
            self.indent(self._list_char[-1].get_width())

    def list_end(self):
        self.dedent()
        self._list_char.pop()

    def header(self):
        tmpl = (
            ".TH %(title_upper)s %(manual_section)s"
            " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n"
            ".SH NAME\n"
            "%(title)s \\- %(subtitle)s\n"
        )
        return tmpl % self._docinfo

    def append_header(self):
        """append header with .TH and .SH NAME"""
        # NOTE before everything
        # .TH title_upper section date source manual
        if self.header_written:
            return
        self.body.append(self.header())
        self.body.append(MACRO_DEF)
        self.header_written = 1

    def visit_address(self, node):
        self.visit_docinfo_item(node, 'address')

    def depart_address(self, node):
        pass

    def visit_admonition(self, node, name=None):
        if name:
            self.body.append('.IP %s\n' % self.language.labels.get(name, name))

    def depart_admonition(self, node):
        self.body.append('.RE\n')

    def visit_attention(self, node):
        self.visit_admonition(node, 'attention')

    depart_attention = depart_admonition

    def visit_docinfo_item(self, node, name):
        if name == 'author':
            self._docinfo[name].append(node.astext())
        else:
            self._docinfo[name] = node.astext()
        self._docinfo_keys.append(name)
        raise nodes.SkipNode()

    def depart_docinfo_item(self, node):
        pass

    def visit_author(self, node):
        self.visit_docinfo_item(node, 'author')

    depart_author = depart_docinfo_item

    def visit_authors(self, node):
        # _author is called anyway.
        pass

    def depart_authors(self, node):
        pass

    def visit_block_quote(self, node):
        # BUG/HACK: indent always uses the _last_ indentation,
        # thus we need two of them.
        self.indent(BLOCKQOUTE_INDENT)
        self.indent(0)

    def depart_block_quote(self, node):
        self.dedent()
        self.dedent()

    def visit_bullet_list(self, node):
        self.list_start(node)

    def depart_bullet_list(self, node):
        self.list_end()

    def visit_caption(self, node):
        pass

    def depart_caption(self, node):
        pass

    def visit_caution(self, node):
        self.visit_admonition(node, 'caution')

    depart_caution = depart_admonition

    def visit_citation(self, node):
        num, text = node.astext().split(None, 1)
        num = num.strip()
        self.body.append('.IP [%s] 5\n' % num)

    def depart_citation(self, node):
        pass

    def visit_citation_reference(self, node):
        self.body.append('[' + node.astext() + ']')
        raise nodes.SkipNode()

    def visit_classifier(self, node):
        pass

    def depart_classifier(self, node):
        pass

    def visit_colspec(self, node):
        self.colspecs.append(node)

    def depart_colspec(self, node):
        pass

    def write_colspecs(self):
        self.body.append("%s.\n" % ('L ' * len(self.colspecs)))

    def visit_comment(self, node, sub=re.compile('-(?=-)').sub):
        self.body.append(self.comment(node.astext()))
        raise nodes.SkipNode()

    def visit_contact(self, node):
        self.visit_docinfo_item(node, 'contact')

    depart_contact = depart_docinfo_item

    def visit_container(self, node):
        pass

    def depart_container(self, node):
        pass

    def visit_compound(self, node):
        pass

    def depart_compound(self, node):
        pass

    def visit_copyright(self, node):
        self.visit_docinfo_item(node, 'copyright')

    def visit_danger(self, node):
        self.visit_admonition(node, 'danger')

    depart_danger = depart_admonition

    def visit_date(self, node):
        self.visit_docinfo_item(node, 'date')

    def visit_decoration(self, node):
        pass

    def depart_decoration(self, node):
        pass

    def visit_definition(self, node):
        pass

    def depart_definition(self, node):
        pass

    def visit_definition_list(self, node):
        self.indent(DEFINITION_LIST_INDENT)

    def depart_definition_list(self, node):
        self.dedent()

    def visit_definition_list_item(self, node):
        self.body.append(self.defs['definition_list_item'][0])

    def depart_definition_list_item(self, node):
        self.body.append(self.defs['definition_list_item'][1])

    def visit_description(self, node):
        pass

    def depart_description(self, node):
        pass

    def visit_docinfo(self, node):
        self._in_docinfo = 1

    def depart_docinfo(self, node):
        self._in_docinfo = None
        # NOTE nothing should be written before this
        self.append_header()

    def visit_doctest_block(self, node):
        self.body.append(self.defs['literal_block'][0])
        self._in_literal = True

    def depart_doctest_block(self, node):
        self._in_literal = False
        self.body.append(self.defs['literal_block'][1])

    def visit_document(self, node):
        # no blank line between comment and header.
        self.body.append(self.comment(self.document_start).rstrip() + '\n')
        # writing header is postboned
        self.header_written = 0

    def depart_document(self, node):
        if self._docinfo['author']:
            self.body.append(
                '.SH AUTHOR\n%s\n' % ', '.join(self._docinfo['author'])
            )
        skip = (
            'author',
            'copyright',
            'date',
            'manual_group',
            'manual_section',
            'subtitle',
            'title',
            'title_upper',
            'version',
        )
        for name in self._docinfo_keys:
            if name == 'address':
                self.body.append(
                    "\n%s:\n%s%s.nf\n%s\n.fi\n%s%s"
                    % (
                        self.language.labels.get(name, name),
                        self.defs['indent'][0] % 0,
                        self.defs['indent'][0] % BLOCKQOUTE_INDENT,
                        self._docinfo[name],
                        self.defs['indent'][1],
                        self.defs['indent'][1],
                    )
                )
            elif name not in skip:
                if name in self._docinfo_names:
                    label = self._docinfo_names[name]
                else:
                    label = self.language.labels.get(name, name)
                self.body.append("\n%s: %s\n" % (label, self._docinfo[name]))
        if self._docinfo['copyright']:
            self.body.append('.SH COPYRIGHT\n%s\n' % self._docinfo['copyright'])
        self.body.append(
            self.comment('Generated by docutils manpage writer.\n')
        )

    def visit_emphasis(self, node):
        self.body.append(self.defs['emphasis'][0])

    def depart_emphasis(self, node):
        self.body.append(self.defs['emphasis'][1])

    def visit_entry(self, node):
        # a cell in a table row
        if 'morerows' in node:
            self.document.reporter.warning(
                '"table row spanning" not supported', base_node=node
            )
        if 'morecols' in node:
            self.document.reporter.warning(
                '"table cell spanning" not supported', base_node=node
            )
        self.context.append(len(self.body))

    def depart_entry(self, node):
        start = self.context.pop()
        self._active_table.append_cell(self.body[start:])
        del self.body[start:]

    def visit_enumerated_list(self, node):
        self.list_start(node)

    def depart_enumerated_list(self, node):
        self.list_end()

    def visit_error(self, node):
        self.visit_admonition(node, 'error')

    depart_error = depart_admonition

    def visit_field(self, node):
        pass

    def depart_field(self, node):
        pass

    def visit_field_body(self, node):
        if self._in_docinfo:
            name_normalized = self._field_name.lower().replace(" ", "_")
            self._docinfo_names[name_normalized] = self._field_name
            self.visit_docinfo_item(node, name_normalized)
            raise nodes.SkipNode()

    def depart_field_body(self, node):
        pass

    def visit_field_list(self, node):
        self.indent(FIELD_LIST_INDENT)

    def depart_field_list(self, node):
        self.dedent()

    def visit_field_name(self, node):
        if self._in_docinfo:
            self._field_name = node.astext()
            raise nodes.SkipNode()
        else:
            self.body.append(self.defs['field_name'][0])

    def depart_field_name(self, node):
        self.body.append(self.defs['field_name'][1])

    def visit_figure(self, node):
        self.indent(2.5)
        self.indent(0)

    def depart_figure(self, node):
        self.dedent()
        self.dedent()

    def visit_footer(self, node):
        self.document.reporter.warning('"footer" not supported', base_node=node)

    def depart_footer(self, node):
        pass

    def visit_footnote(self, node):
        num, text = node.astext().split(None, 1)
        num = num.strip()
        self.body.append('.IP [%s] 5\n' % self.deunicode(num))

    def depart_footnote(self, node):
        pass

    def footnote_backrefs(self, node):
        self.document.reporter.warning(
            '"footnote_backrefs" not supported', base_node=node
        )

    def visit_footnote_reference(self, node):
        self.body.append('[' + self.deunicode(node.astext()) + ']')
        raise nodes.SkipNode()

    def depart_footnote_reference(self, node):
        pass

    def visit_generated(self, node):
        pass

    def depart_generated(self, node):
        pass

    def visit_header(self, node):
        raise NotImplementedError(node.astext())

    def depart_header(self, node):
        pass

    def visit_hint(self, node):
        self.visit_admonition(node, 'hint')

    depart_hint = depart_admonition

    def visit_subscript(self, node):
        self.body.append('\\s-2\\d')

    def depart_subscript(self, node):
        self.body.append('\\u\\s0')

    def visit_superscript(self, node):
        self.body.append('\\s-2\\u')

    def depart_superscript(self, node):
        self.body.append('\\d\\s0')

    def visit_attribution(self, node):
        self.body.append('\\(em ')

    def depart_attribution(self, node):
        self.body.append('\n')

    def visit_image(self, node):
        self.document.reporter.warning('"image" not supported', base_node=node)
        text = []
        if 'alt' in node.attributes:
            text.append(node.attributes['alt'])
        if 'uri' in node.attributes:
            text.append(node.attributes['uri'])
        self.body.append('[image: %s]\n' % ('/'.join(text)))
        raise nodes.SkipNode()

    def visit_important(self, node):
        self.visit_admonition(node, 'important')

    depart_important = depart_admonition

    def visit_label(self, node):
        # footnote and citation
        if isinstance(node.parent, nodes.footnote) or isinstance(
            node.parent, nodes.citation
        ):
            raise nodes.SkipNode()
        self.document.reporter.warning('"unsupported "label"', base_node=node)
        self.body.append('[')

    def depart_label(self, node):
        self.body.append(']\n')

    def visit_legend(self, node):
        pass

    def depart_legend(self, node):
        pass

    # WHAT should we use .INDENT, .UNINDENT ?
    def visit_line_block(self, node):
        self._line_block += 1
        if self._line_block == 1:
            self.body.append('.sp\n')
            self.body.append('.nf\n')
        else:
            self.body.append('.in +2\n')

    def depart_line_block(self, node):
        self._line_block -= 1
        if self._line_block == 0:
            self.body.append('.fi\n')
            self.body.append('.sp\n')
        else:
            self.body.append('.in -2\n')

    def visit_line(self, node):
        pass

    def depart_line(self, node):
        self.body.append('\n')

    def visit_list_item(self, node):
        # man 7 man argues to use ".IP" instead of ".TP"
        self.body.append(
            '.IP %s %d\n'
            % (
                next(self._list_char[-1]),
                self._list_char[-1].get_width(),
            )
        )

    def depart_list_item(self, node):
        pass

    def visit_literal(self, node):
        self.body.append(self.defs['literal'][0])

    def depart_literal(self, node):
        self.body.append(self.defs['literal'][1])

    def visit_literal_block(self, node):
        self.body.append(self.defs['literal_block'][0])
        self._in_literal = True

    def depart_literal_block(self, node):
        self._in_literal = False
        self.body.append(self.defs['literal_block'][1])

    def visit_meta(self, node):
        raise NotImplementedError(node.astext())

    def depart_meta(self, node):
        pass

    def visit_note(self, node):
        self.visit_admonition(node, 'note')

    depart_note = depart_admonition

    def indent(self, by=0.5):
        # if we are in a section ".SH" there already is a .RS
        step = self._indent[-1]
        self._indent.append(by)
        self.body.append(self.defs['indent'][0] % step)

    def dedent(self):
        self._indent.pop()
        self.body.append(self.defs['indent'][1])

    def visit_option_list(self, node):
        self.indent(OPTION_LIST_INDENT)

    def depart_option_list(self, node):
        self.dedent()

    def visit_option_list_item(self, node):
        # one item of the list
        self.body.append(self.defs['option_list_item'][0])

    def depart_option_list_item(self, node):
        self.body.append(self.defs['option_list_item'][1])

    def visit_option_group(self, node):
        # as one option could have several forms it is a group
        # options without parameter bold only, .B, -v
        # options with parameter bold italic, .BI, -f file
        #
        # we do not know if .B or .BI
        self.context.append('.B')  # blind guess
        self.context.append(len(self.body))  # to be able to insert later
        self.context.append(0)  # option counter

    def depart_option_group(self, node):
        self.context.pop()  # the counter
        start_position = self.context.pop()
        text = self.body[start_position:]
        del self.body[start_position:]
        self.body.append('%s%s\n' % (self.context.pop(), ''.join(text)))

    def visit_option(self, node):
        # each form of the option will be presented separately
        if self.context[-1] > 0:
            self.body.append(', ')
        if self.context[-3] == '.BI':
            self.body.append('\\')
        self.body.append(' ')

    def depart_option(self, node):
        self.context[-1] += 1

    def visit_option_string(self, node):
        # do not know if .B or .BI
        pass

    def depart_option_string(self, node):
        pass

    def visit_option_argument(self, node):
        self.context[-3] = '.BI'  # bold/italic alternate
        if node['delimiter'] != ' ':
            self.body.append('\\fB%s ' % node['delimiter'])
        elif self.body[len(self.body) - 1].endswith('='):
            # a blank only means no blank in output, just changing font
            self.body.append(' ')
        else:
            # blank backslash blank, switch font then a blank
            self.body.append(' \\ ')

    def depart_option_argument(self, node):
        pass

    def visit_organization(self, node):
        self.visit_docinfo_item(node, 'organization')

    def depart_organization(self, node):
        pass

    def visit_paragraph(self, node):
        # ``.PP`` : Start standard indented paragraph.
        # ``.LP`` : Start block paragraph, all except the first.
        # ``.P [type]``  : Start paragraph type.
        # NOTE don't use paragraph starts because they reset indentation.
        # ``.sp`` is only vertical space
        self.ensure_eol()
        self.body.append('.sp\n')

    def depart_paragraph(self, node):
        self.body.append('\n')

    def visit_problematic(self, node):
        self.body.append(self.defs['problematic'][0])

    def depart_problematic(self, node):
        self.body.append(self.defs['problematic'][1])

    def visit_raw(self, node):
        if node.get('format') == 'manpage':
            self.body.append(node.astext() + "\n")
        # Keep non-manpage raw text out of output:
        raise nodes.SkipNode()

    def visit_reference(self, node):
        """E.g. link or email address."""
        self.body.append(self.defs['reference'][0])

    def depart_reference(self, node):
        self.body.append(self.defs['reference'][1])

    def visit_revision(self, node):
        self.visit_docinfo_item(node, 'revision')

    depart_revision = depart_docinfo_item

    def visit_row(self, node):
        self._active_table.new_row()

    def depart_row(self, node):
        pass

    def visit_section(self, node):
        self.section_level += 1

    def depart_section(self, node):
        self.section_level -= 1

    def visit_status(self, node):
        self.visit_docinfo_item(node, 'status')

    depart_status = depart_docinfo_item

    def visit_strong(self, node):
        self.body.append(self.defs['strong'][0])

    def depart_strong(self, node):
        self.body.append(self.defs['strong'][1])

    def visit_substitution_definition(self, node):
        """Internal only."""
        raise nodes.SkipNode()

    def visit_substitution_reference(self, node):
        self.document.reporter.warning(
            '"substitution_reference" not supported', base_node=node
        )

    def visit_subtitle(self, node):
        if isinstance(node.parent, nodes.sidebar):
            self.body.append(self.defs['strong'][0])
        elif isinstance(node.parent, nodes.document):
            self.visit_docinfo_item(node, 'subtitle')
        elif isinstance(node.parent, nodes.section):
            self.body.append(self.defs['strong'][0])

    def depart_subtitle(self, node):
        # document subtitle calls SkipNode
        self.body.append(self.defs['strong'][1] + '\n.PP\n')

    def visit_system_message(self, node):
        # TODO add report_level
        # if node['level'] < self.document.reporter['writer'].report_level:
        #    Level is too low to display:
        #    raise nodes.SkipNode
        attr = {}
        if node.hasattr('id'):
            attr['name'] = node['id']
        if node.hasattr('line'):
            line = ', line %s' % node['line']
        else:
            line = ''
        self.body.append(
            '.IP "System Message: %s/%s (%s:%s)"\n'
            % (node['type'], node['level'], node['source'], line)
        )

    def depart_system_message(self, node):
        pass

    def visit_table(self, node):
        self._active_table = Table()

    def depart_table(self, node):
        self.ensure_eol()
        self.body.extend(self._active_table.as_list())
        self._active_table = None

    def visit_target(self, node):
        # targets are in-document hyper targets, without any use for man-pages.
        raise nodes.SkipNode()

    def visit_tbody(self, node):
        pass

    def depart_tbody(self, node):
        pass

    def visit_term(self, node):
        self.body.append(self.defs['term'][0])

    def depart_term(self, node):
        self.body.append(self.defs['term'][1])

    def visit_tgroup(self, node):
        pass

    def depart_tgroup(self, node):
        pass

    def visit_thead(self, node):
        # MAYBE double line '='
        pass

    def depart_thead(self, node):
        # MAYBE double line '='
        pass

    def visit_tip(self, node):
        self.visit_admonition(node, 'tip')

    depart_tip = depart_admonition

    def visit_title(self, node):
        if isinstance(node.parent, nodes.topic):
            self.body.append(self.defs['topic-title'][0])
        elif isinstance(node.parent, nodes.sidebar):
            self.body.append(self.defs['sidebar-title'][0])
        elif isinstance(node.parent, nodes.admonition):
            self.body.append('.IP "')
        elif self.section_level == 0:
            self._docinfo['title'] = node.astext()
            # document title for .TH
            self._docinfo['title_upper'] = node.astext().upper()
            raise nodes.SkipNode()
        elif self.section_level == 1:
            self.body.append('.SH ')
            for n in node.traverse(nodes.Text):
                n.parent.replace(n, nodes.Text(n.astext().upper()))
        else:
            self.body.append('.SS ')

    def depart_title(self, node):
        if isinstance(node.parent, nodes.admonition):
            self.body.append('"')
        self.body.append('\n')

    def visit_title_reference(self, node):
        """inline citation reference"""
        self.body.append(self.defs['title_reference'][0])

    def depart_title_reference(self, node):
        self.body.append(self.defs['title_reference'][1])

    def visit_topic(self, node):
        pass

    def depart_topic(self, node):
        pass

    def visit_sidebar(self, node):
        pass

    def depart_sidebar(self, node):
        pass

    def visit_rubric(self, node):
        pass

    def depart_rubric(self, node):
        pass

    def visit_transition(self, node):
        # .PP      Begin a new paragraph and reset prevailing indent.
        # .sp N    leaves N lines of blank space.
        # .ce      centers the next line
        self.body.append('\n.sp\n.ce\n----\n')

    def depart_transition(self, node):
        self.body.append('\n.ce 0\n.sp\n')

    def visit_version(self, node):
        self.visit_docinfo_item(node, 'version')

    def visit_warning(self, node):
        self.visit_admonition(node, 'warning')

    depart_warning = depart_admonition

    def unimplemented_visit(self, node):
        raise NotImplementedError(
            'visiting unimplemented node type: %s' % node.__class__.__name__
        )


# vim: set fileencoding=utf-8 et ts=4 ai :