Compare commits
47 Commits
c682ac247c
...
d0f9f0ae83
Author | SHA1 | Date |
---|---|---|
Thomas Touhey | d0f9f0ae83 | |
Thomas Touhey | 9d3ca92b81 | |
Thomas Touhey | c767082461 | |
Thomas Touhey | f1f1be481a | |
Thomas Touhey | 0df1699c27 | |
Thomas Touhey | 55946468fd | |
Thomas Touhey | b1c0968494 | |
Thomas Touhey | 4dd686e20a | |
Thomas Touhey | 5076d0b800 | |
Thomas Touhey | 774d427fda | |
Thomas Touhey | fb75b482ba | |
Thomas Touhey | d1c6d09fc1 | |
Thomas Touhey | d39a356194 | |
Thomas Touhey | 2ba11bc6f6 | |
Thomas Touhey | 516726c447 | |
Thomas Touhey | dcb8611e37 | |
Thomas Touhey | dd19f7cf20 | |
Thomas Touhey | ea98e48a15 | |
Thomas Touhey | 4774189495 | |
Thomas Touhey | 30f5dd7fd6 | |
Thomas Touhey | 261f9eb486 | |
Thomas Touhey | ec66264d81 | |
Thomas Touhey | 638e5c9195 | |
Thomas Touhey | d061fabfd8 | |
Thomas Touhey | 8459e04f29 | |
Thomas Touhey | 5e9790b613 | |
Thomas Touhey | 8f7732a07f | |
Thomas Touhey | 1f80dd6772 | |
Thomas Touhey | 64c49b0ace | |
Thomas Touhey | 5acbc428cd | |
Thomas Touhey | bb36d42a54 | |
Thomas Touhey | a6d6f62d11 | |
Thomas Touhey | 3b3b54312a | |
Thomas Touhey | 8d9d4360ed | |
Thomas Touhey | a2fdc5d5a0 | |
Thomas Touhey | 41fd882e19 | |
Thomas Touhey | 9a4131b074 | |
Thomas Touhey | 902a23e23e | |
Thomas Touhey | 4b47e63fff | |
Thomas Touhey | f162d1b94c | |
Thomas Touhey | f4ee519eb3 | |
Thomas Touhey | 3cdd2b4bc1 | |
Thomas Touhey | cfed4cc7de | |
Thomas Touhey | d9e6f27979 | |
Thomas Touhey | 167438a97f | |
Thomas Touhey | 5920a0860d | |
Thomas Touhey | 2c2ce72719 |
|
@ -3,3 +3,7 @@ __pycache__
|
|||
/*.egg-info
|
||||
/dist
|
||||
/.spyproject
|
||||
/build
|
||||
/docs/_build
|
||||
/venv
|
||||
/README.html
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
3.7.2
|
|
@ -1,5 +1,6 @@
|
|||
# The MIT License (MIT)
|
||||
Copyright (C) 2018 Thomas Touhey <<thomas@touhey.fr>>
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2018-2019 Thomas Touhey <thomas@touhey.fr>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the “Software”), to deal
|
|
@ -0,0 +1,10 @@
|
|||
include README.rst
|
||||
include LICENSE.txt
|
||||
include MANIFEST.in
|
||||
include setup.py
|
||||
include setup.cfg
|
||||
|
||||
include docs/*.rst
|
||||
include docs/conf.py
|
||||
include docs/Makefile
|
||||
include docs/make.bat
|
18
Makefile
18
Makefile
|
@ -1,9 +1,21 @@
|
|||
#!/usr/bin/make -f
|
||||
ST := ./setup.py
|
||||
PE := pipenv run
|
||||
ST := $(PE) ./setup.py
|
||||
DNAME := dist/$(shell $(ST) --name)-$(shell $(ST) --version).tar.gz
|
||||
|
||||
test tests:
|
||||
@$(ST) test
|
||||
@$(PE) pytest -s -q
|
||||
|
||||
prepare:
|
||||
@pipenv install --dev
|
||||
update:
|
||||
@pipenv update --dev
|
||||
|
||||
docs:
|
||||
@$(ST) build_sphinx
|
||||
|
||||
checkdocs:
|
||||
@$(ST) checkdocs
|
||||
|
||||
dist: $(DNAME)
|
||||
$(DNAME):
|
||||
|
@ -12,5 +24,5 @@ $(DNAME):
|
|||
upload: $(DNAME)
|
||||
@twine upload $(DNAME)
|
||||
|
||||
.PHONY: test tests dist
|
||||
.PHONY: test tests dist docs
|
||||
# End of file.
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
[[source]]
|
||||
url = 'https://pypi.python.org/simple'
|
||||
verify_ssl = true
|
||||
name = 'pypi'
|
||||
|
||||
[requires]
|
||||
python_version = '3.7'
|
||||
|
||||
[packages]
|
||||
regex = '*'
|
||||
thcolor = '*'
|
||||
|
||||
[dev-packages]
|
||||
sphinx = '*'
|
||||
"collective.checkdocs" = '*'
|
||||
pudb = '*'
|
||||
pytest = '*'
|
|
@ -0,0 +1,311 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "f2cea3a09ae48de290ab5aa3af45cedaf158f9586a8f84c50c1825de25732522"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
"python_version": "3.7"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"name": "pypi",
|
||||
"url": "https://pypi.python.org/simple",
|
||||
"verify_ssl": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": {
|
||||
"regex": {
|
||||
"hashes": [
|
||||
"sha256:020429dcf9b76cc7648a99c81b3a70154e45afebc81e0b85364457fe83b525e4",
|
||||
"sha256:0552802b1c3f3c7e4fee8c85e904a13c48226020aa1a0593246888a1ac55aaaf",
|
||||
"sha256:308965a80b92e1fec263ac1e4f1094317809a72bc4d26be2ec8a5fd026301175",
|
||||
"sha256:4d627feef04eb626397aa7bdec772774f53d63a1dc7cc5ee4d1bd2786a769d19",
|
||||
"sha256:93d1f9fcb1d25e0b4bd622eeba95b080262e7f8f55e5b43c76b8a5677e67334c",
|
||||
"sha256:c3859bbf29b1345d694f069ddfe53d6907b0393fda5e3794c800ad02902d78e9",
|
||||
"sha256:d56ce4c7b1a189094b9bee3b81c4aeb3f1ba3e375e91627ec8561b6ab483d0a8",
|
||||
"sha256:ebc5ef4e10fa3312fa1967dc0a894e6bd985a046768171f042ac3974fadc9680",
|
||||
"sha256:f9cd39066048066a4abe4c18fb213bc541339728005e72263f023742fb912585"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2019.4.14"
|
||||
},
|
||||
"thcolor": {
|
||||
"hashes": [
|
||||
"sha256:a93a535f7f81b5e38460531b5731f0fa8c4b30b2a63d1fe763c2ac15e37df8b7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.3.1"
|
||||
}
|
||||
},
|
||||
"develop": {
|
||||
"alabaster": {
|
||||
"hashes": [
|
||||
"sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
|
||||
"sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"
|
||||
],
|
||||
"version": "==0.7.12"
|
||||
},
|
||||
"atomicwrites": {
|
||||
"hashes": [
|
||||
"sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4",
|
||||
"sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"
|
||||
],
|
||||
"version": "==1.3.0"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79",
|
||||
"sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"
|
||||
],
|
||||
"version": "==19.1.0"
|
||||
},
|
||||
"babel": {
|
||||
"hashes": [
|
||||
"sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669",
|
||||
"sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23"
|
||||
],
|
||||
"version": "==2.6.0"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5",
|
||||
"sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae"
|
||||
],
|
||||
"version": "==2019.3.9"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
|
||||
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
|
||||
],
|
||||
"version": "==3.0.4"
|
||||
},
|
||||
"collective.checkdocs": {
|
||||
"hashes": [
|
||||
"sha256:3a5328257c5224bc72753820c182910d7fb336bc1dba5e09113d48566655e46e"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.2"
|
||||
},
|
||||
"docutils": {
|
||||
"hashes": [
|
||||
"sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
|
||||
"sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
|
||||
"sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
|
||||
],
|
||||
"version": "==0.14"
|
||||
},
|
||||
"idna": {
|
||||
"hashes": [
|
||||
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
|
||||
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
|
||||
],
|
||||
"version": "==2.8"
|
||||
},
|
||||
"imagesize": {
|
||||
"hashes": [
|
||||
"sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8",
|
||||
"sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
|
||||
"sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
|
||||
],
|
||||
"version": "==2.10.1"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
|
||||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"more-itertools": {
|
||||
"hashes": [
|
||||
"sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7",
|
||||
"sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a"
|
||||
],
|
||||
"markers": "python_version > '2.7'",
|
||||
"version": "==7.0.0"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
|
||||
"sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
|
||||
],
|
||||
"version": "==19.0"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
"sha256:25a1bc1d148c9a640211872b4ff859878d422bccb59c9965e04eed468a0aa180",
|
||||
"sha256:964cedd2b27c492fbf0b7f58b3284a09cf7f99b0f715941fb24a439b3af1bd1a"
|
||||
],
|
||||
"version": "==0.11.0"
|
||||
},
|
||||
"pudb": {
|
||||
"hashes": [
|
||||
"sha256:ac30cfc64580958ab7265decb4cabb9141f08781ff072e9a336d5a7942ce35a6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2019.1"
|
||||
},
|
||||
"py": {
|
||||
"hashes": [
|
||||
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa",
|
||||
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"
|
||||
],
|
||||
"version": "==1.8.0"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:31cba6ffb739f099a85e243eff8cb717089fdd3c7300767d9fc34cb8e1b065f5",
|
||||
"sha256:5ad302949b3c98dd73f8d9fcdc7e9cb592f120e32a18e23efd7f3dc51194472b"
|
||||
],
|
||||
"version": "==2.4.0"
|
||||
},
|
||||
"pyparsing": {
|
||||
"hashes": [
|
||||
"sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a",
|
||||
"sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03"
|
||||
],
|
||||
"version": "==2.4.0"
|
||||
},
|
||||
"pytest": {
|
||||
"hashes": [
|
||||
"sha256:1a8aa4fa958f8f451ac5441f3ac130d9fc86ea38780dd2715e6d5c5882700b24",
|
||||
"sha256:b8bf138592384bd4e87338cb0f256bf5f615398a649d4bd83915f0e4047a5ca6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.5.0"
|
||||
},
|
||||
"pytz": {
|
||||
"hashes": [
|
||||
"sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda",
|
||||
"sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141"
|
||||
],
|
||||
"version": "==2019.1"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
|
||||
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
|
||||
],
|
||||
"version": "==2.21.0"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
|
||||
"sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
|
||||
],
|
||||
"version": "==1.12.0"
|
||||
},
|
||||
"snowballstemmer": {
|
||||
"hashes": [
|
||||
"sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128",
|
||||
"sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89"
|
||||
],
|
||||
"version": "==1.2.1"
|
||||
},
|
||||
"sphinx": {
|
||||
"hashes": [
|
||||
"sha256:423280646fb37944dd3c85c58fb92a20d745793a9f6c511f59da82fa97cd404b",
|
||||
"sha256:de930f42600a4fef993587633984cc5027dedba2464bcf00ddace26b40f8d9ce"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"sphinxcontrib-applehelp": {
|
||||
"hashes": [
|
||||
"sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897",
|
||||
"sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"sphinxcontrib-devhelp": {
|
||||
"hashes": [
|
||||
"sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34",
|
||||
"sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"sphinxcontrib-htmlhelp": {
|
||||
"hashes": [
|
||||
"sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422",
|
||||
"sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-jsmath": {
|
||||
"hashes": [
|
||||
"sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178",
|
||||
"sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"
|
||||
],
|
||||
"version": "==1.0.1"
|
||||
},
|
||||
"sphinxcontrib-qthelp": {
|
||||
"hashes": [
|
||||
"sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20",
|
||||
"sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f"
|
||||
],
|
||||
"version": "==1.0.2"
|
||||
},
|
||||
"sphinxcontrib-serializinghtml": {
|
||||
"hashes": [
|
||||
"sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227",
|
||||
"sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768"
|
||||
],
|
||||
"version": "==1.1.3"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4",
|
||||
"sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb"
|
||||
],
|
||||
"version": "==1.24.3"
|
||||
},
|
||||
"urwid": {
|
||||
"hashes": [
|
||||
"sha256:644d3e3900867161a2fc9287a9762753d66bd194754679adb26aede559bcccbc"
|
||||
],
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e",
|
||||
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"
|
||||
],
|
||||
"version": "==0.1.7"
|
||||
}
|
||||
}
|
||||
}
|
105
README.rst
105
README.rst
|
@ -1,99 +1,34 @@
|
|||
Planète Casio's textout() BBcode markup language translator
|
||||
===========================================================
|
||||
|
||||
This module contains a BBcode to HTML translator for
|
||||
`Planète Casio`_. For more information, read the
|
||||
documentation accessible on `the official website`_.
|
||||
|
||||
.. warning::
|
||||
|
||||
If you are accessing this repository from <https://git.planet-casio.com>_,
|
||||
If you are accessing this repository from another forge (such as
|
||||
`Planète Casio's forge <https://gitea.planet-casio.com/cake/textout>`_),
|
||||
keep in mind that it is only a mirror and that the real repository
|
||||
is located at <https://forge.touhey.fr/pc/textout.git>_ for now.
|
||||
is located `in my forge <https://forge.touhey.org/pc/textout.git>`_
|
||||
for now.
|
||||
|
||||
BBcode has been invented in the 90s/2000s for bulletin board systems.
|
||||
It has been implemented in `Planète Casio`_ during its first years (although
|
||||
some research has to be made on how that choice was done…).
|
||||
|
||||
On `Planète Casio`_, which is coded in PHP at the time I'm writing this,
|
||||
we have our own custom version of BBcode, which we pass through an internal
|
||||
utility named ``textout()``.
|
||||
|
||||
I, Thomas “Cakeisalie5” Touhey, rewrote it recently, and it works pretty well
|
||||
while being secure, but as the next version of `Planète Casio`_ (the ”v5”)
|
||||
will be written from scratch, I figured out I could rewrite the ``textout()``
|
||||
utility in Python, and improve the language parsing to be more practical and
|
||||
add features that are in the original BBcode markup language.
|
||||
|
||||
As this is a rewrite, the vulnerabilities and bug will not be common to this
|
||||
project and the online version of the transcoder.
|
||||
|
||||
-----
|
||||
Usage
|
||||
-----
|
||||
|
||||
To use this module, simply use the ``to<language>()`` functions once imported:
|
||||
For example, if we want to translate some basic multi-block text to HTML:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import textoutpc
|
||||
from textoutpc import tohtml
|
||||
|
||||
text = "Hello, [i]beautiful [b]world[/i]!"
|
||||
print(textoutpc.tohtml(text))
|
||||
print("---")
|
||||
print(textoutpc.tolightscript(text))
|
||||
text = """[img=center]https://www.planet-casio.com/skins/bootstrap/img/default/logo.png[/img]
|
||||
|
||||
The supported output types are:
|
||||
Hello [color=R10]world[/color]!
|
||||
[ul]
|
||||
[*]This module is made by [url=https://thomas.touhey.fr/]Thomas Touhey[/url]!
|
||||
[*]Use `.tohtml()` to translate magically to HTML!
|
||||
[/]
|
||||
"""
|
||||
|
||||
- ``html``: `HTML`_ compatible output, requiring some additional style and
|
||||
script;
|
||||
- ``lightscript``: `Lightscript`_ Markdown-like language. See
|
||||
`the Lightscript topic on Planète Casio <Lightscript topic>`_ for
|
||||
more information.
|
||||
print(tohtml(text))
|
||||
|
||||
------
|
||||
Tweaks
|
||||
------
|
||||
|
||||
The ``tohtml()`` and ``tolightscript()`` can take additional keywords that
|
||||
tags can read so that they can adapt their behaviour. The name of the tweaks
|
||||
are case-insensitive and non-alphanumeric characters are ignored: for example,
|
||||
``label_prefix``, ``LABELPREFIX`` and ``__LaBeL___PRE_FIX__`` are all
|
||||
equivalent.
|
||||
|
||||
The following tweaks are read by the translator and built-in tags:
|
||||
|
||||
- ``label_prefix`` (HTML): prefix to be used by the ``[label]`` and
|
||||
``[target]`` tags, e.g. ``msg45529-``. Defaults to `""` for PCv42
|
||||
compatibility;
|
||||
- ``obsolete_tags`` (HTML): use obsolete HTML tags for old browsers
|
||||
(e.g. lynx) compatibility, e.g. ``<b>``, ``<i>``, ``<center>``, and
|
||||
others. Defaults to ``True``.
|
||||
|
||||
An example call would be:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import textoutpc
|
||||
|
||||
print(textoutpc.tohtml("Hello, [i]beautiful[/i]!", obsolete__TAGS=False))
|
||||
|
||||
------------------
|
||||
What is left to do
|
||||
------------------
|
||||
|
||||
- Correct the translator until all the tests pass;
|
||||
- Manage blocks superseeding each other;
|
||||
- Implement BBcode lists using ``[*]``, ``[**]``, …;
|
||||
- Manage lightscript (or even markdown?) as output languages;
|
||||
- Check where the errors are to display them to the user:
|
||||
|
||||
* Count character offset, line number and column number in the lexer;
|
||||
* Produce readable exceptions;
|
||||
* Make a clean interface to transmit them;
|
||||
- Check why exceptions on raw tags effectively escape the content, as it
|
||||
shouldn't…?
|
||||
- Look for security flaws (we really don't want stored XSS flaws!).
|
||||
|
||||
.. _Planète Casio: https://www.planet-casio.com/Fr/
|
||||
.. _HTML: https://www.w3.org/html/
|
||||
.. _Lightscript: https://git.planet-casio.com/lephe/lightscript
|
||||
.. _Lightscript topic: https://planet-casio.com/Fr/forums/lecture_sujet.php?id=15022
|
||||
.. _Planète Casio: https://www.planet-casio.com/
|
||||
.. _the official website: https://textout.touhey.pro/
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
What is left to do
|
||||
==================
|
||||
|
||||
- Add an ``[imgurl]`` tag?
|
||||
- Manage lightscript (or even markdown?) as output languages;
|
||||
- Check where the errors are to display them to the user:
|
||||
|
||||
* Count character offset, line number and column number in the lexer;
|
||||
* Produce readable exceptions;
|
||||
* Make a clean interface to transmit them;
|
||||
- Check why exceptions on raw tags effectively escape the content, as it
|
||||
shouldn't…?
|
||||
- Look for security flaws (we really don't want stored XSS flaws!).
|
||||
- Implement match names (such as ``\[\*+\]`` for lists).
|
||||
- Manage keywords with tags such as ``[tag key=value other="something else"]``.
|
|
@ -0,0 +1,20 @@
|
|||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXPROJ = textoutpc
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
|
@ -0,0 +1,167 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# This file does only contain a selection of the most common options. For a
|
||||
# full list see the documentation:
|
||||
# http://www.sphinx-doc.org/en/master/config
|
||||
|
||||
# -- Path setup --------------------------------------------------------------
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
|
||||
def _add_paths():
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
_add_paths()
|
||||
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'textoutpc'
|
||||
copyright = '2019, Thomas Touhey'
|
||||
author = 'Thomas Touhey'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
|
||||
def _get_release():
|
||||
from os.path import dirname, join
|
||||
from pkg_resources import find_distributions as find_dist
|
||||
|
||||
module_path = join(dirname(__file__), '..')
|
||||
dist = next(find_dist(module_path, True))
|
||||
return dist.version
|
||||
|
||||
release = _get_release()
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = []
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
# This pattern also affects html_static_path and html_extra_path .
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.goutput*']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#
|
||||
# html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = []
|
||||
|
||||
# Custom sidebar templates, must be a dictionary that maps document names
|
||||
# to template names.
|
||||
#
|
||||
# The default sidebars (for documents that don't match any pattern) are
|
||||
# defined by theme itself. Builtin themes are using these templates by
|
||||
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
||||
# 'searchbox.html']``.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
|
||||
# -- Options for HTMLHelp output ---------------------------------------------
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'textoutpcdoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output ------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#
|
||||
# 'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'textoutpc.tex', 'textoutpc Documentation',
|
||||
'Thomas Touhey', 'manual'),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for manual page output ------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'textoutpc', 'textoutpc Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Texinfo output ----------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'textoutpc', 'textoutpc Documentation',
|
||||
author, 'textoutpc', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
|
@ -0,0 +1,31 @@
|
|||
Welcome to textoutpc's documentation!
|
||||
=====================================
|
||||
|
||||
textoutpc is a BBcode markup language translator project for `Planète Casio`_.
|
||||
|
||||
BBcode has been invented in the 90s/2000s for bulletin board systems.
|
||||
It has been implemented in `Planète Casio`_ during its first years (although
|
||||
some research has to be made on how that choice was done…).
|
||||
|
||||
On `Planète Casio`_, which is coded in PHP at the time I'm writing this,
|
||||
we have our own custom version of BBcode, which we pass through an internal
|
||||
utility named ``textout()``.
|
||||
|
||||
I, Thomas “Cakeisalie5” Touhey, rewrote it recently, and it works pretty well
|
||||
while being secure, but as the next version of `Planète Casio`_ (the ”v5”)
|
||||
will be written from scratch, I figured out I could rewrite the ``textout()``
|
||||
utility in Python, and improve the language parsing to be more practical and
|
||||
add features that are in the original BBcode markup language.
|
||||
|
||||
As this is a rewrite, the vulnerabilities and bug will not be common to this
|
||||
project and the online version of the transcoder.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
language
|
||||
usage
|
||||
tags
|
||||
|
||||
.. _Planète Casio: https://www.planet-casio.com/Fr/
|
|
@ -1,4 +1,4 @@
|
|||
User's guide to the textout BBcode
|
||||
The textout BBcode markup language
|
||||
==================================
|
||||
|
||||
The BBcode markup language mainly uses tags, which the starting mark looks
|
||||
|
@ -52,9 +52,10 @@ You can add some basic text style by using the following tags:
|
|||
- ``[b]`` for **bold** text;
|
||||
- ``[i]`` for *italic* text;
|
||||
- ``[u]`` for underlined text;
|
||||
- ``[s]`` or ``[strike]`` for :strike:`striked` text.
|
||||
- ``[o]`` for overlined text;
|
||||
- ``[s]`` or ``[strike]`` for striked text.
|
||||
|
||||
They can all be ended with the generic ending mark `[/]`.
|
||||
They can all be ended with the generic ending mark ``[/]``.
|
||||
|
||||
----------------------
|
||||
Changing the text font
|
||||
|
@ -81,15 +82,16 @@ You can change the color of the text using the ``[color=xxx]`` (or ``[xxx]``
|
|||
directly for simple colors, where ``xxx`` represents the color) tag. This
|
||||
tag accepts several types of values:
|
||||
|
||||
- simple color names (`inspired from CSS <CSS named colors>`_) such as ``red``,
|
||||
``blue``, ``green``, ``transparent``;
|
||||
- color hex codes using ``#`` followed by hex digits, e.g. ``#01020F``,
|
||||
where the first group of hex digits represents the red component from
|
||||
0 to 255, the second group of hex digits represents the green component
|
||||
from 0 to 255, and the third group of hex digits represents the blue
|
||||
component from 0 to 255. Incomplete composites will be filled by zero
|
||||
on the left (e.g. ``#0000F`` is equivalent to ``#00000F``), invalid
|
||||
characters such as `A` will be replaced by `0`s;
|
||||
- simple color names (`inspired from CSS <CSS named colors_>`_) such as
|
||||
``red``, ``blue``, ``green``, ``transparent``;
|
||||
- color hex codes using a hash (``#``) followed by hex digits, e.g.
|
||||
``#01020F``, where the first group of hex digits represents the
|
||||
red component from 0 to 255, the second group of hex digits represents
|
||||
the green component from 0 to 255, and the third group of hex digits
|
||||
represents the blue component from 0 to 255.
|
||||
Incomplete composites will be filled by zero on the left (e.g. ``#0000F``
|
||||
is equivalent to ``#00000F``), invalid characters such as ``A`` will be
|
||||
replaced by 0s;
|
||||
- three hex digits codes using ``#`` followed by three hex digits, e.g.
|
||||
``#123`` which will be translated to ``#112233``;
|
||||
- ``rgb(<red>, <green>, <blue>)``, where the red, green and blue components
|
||||
|
@ -109,6 +111,7 @@ Here are some examples:
|
|||
.. code::
|
||||
|
||||
[blue]I'm blue![/]
|
||||
[color=#ff69b4]That color is called “Cuisse de Nymphe émue”![/]
|
||||
[color=rgb(255, 255,255,0.4)]I'm black![/]
|
||||
[color=hsl(0,100%, 0.5)]I'm red![/]
|
||||
|
||||
|
@ -131,14 +134,32 @@ Here are examples:
|
|||
[url=https://planet-casio.com]Planète Casio[/]
|
||||
[url=/relative/url.html][/]
|
||||
|
||||
To link to profiles, the ``[profil]`` and ``[profile]`` tags can be used. They
|
||||
take no attribute but take a content which is the user whose the profile
|
||||
For links to profiles, the ``[profil]`` and ``[profile]`` tags can be used.
|
||||
They take no attribute but take a content which is the user whose the profile
|
||||
is to be linked's name. For example:
|
||||
|
||||
.. code::
|
||||
|
||||
[profil]Cakeisalie5[/]
|
||||
|
||||
For links to topics and tutorials, the ``[topic]`` and ``[tutorial]``
|
||||
tags can be used. They take no attribute but take a content which is the
|
||||
identifier of the topic or tutorial to which to link to.
|
||||
For example:
|
||||
|
||||
.. code::
|
||||
|
||||
[topic]234[/]
|
||||
[tutorial]32[/]
|
||||
|
||||
For links to programs, the ``[program]`` and ``[prog]`` tags can be used.
|
||||
They take no attribute but take a content which is the identifier of the
|
||||
program to which to link to. For example:
|
||||
|
||||
.. code::
|
||||
|
||||
[program]3598[/program]
|
||||
|
||||
---------------
|
||||
Quoting someone
|
||||
---------------
|
|
@ -0,0 +1,36 @@
|
|||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=.
|
||||
set BUILDDIR=_build
|
||||
set SPHINXPROJ=textoutpc
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
||||
|
||||
:end
|
||||
popd
|
|
@ -1,5 +1,5 @@
|
|||
Adding a tag in the Python port of Planète Casio's textout
|
||||
==========================================================
|
||||
Defining tags for textoutpc
|
||||
===========================
|
||||
|
||||
A tag is a class defined in the ``textoutpc.Tags`` submodule and referenced
|
||||
in the `_tags` array in ``textoutpc.Tags.__init__``. It usually takes a name
|
||||
|
@ -16,25 +16,37 @@ There are a few public members you can define as a tag:
|
|||
- ``aliases``: the array of names this tag can be accessed as.
|
||||
Basic tags (the ones with brackets ``[]``) are defined as ``[<name>]``
|
||||
(e.g. ``[hello]``) and special characters are defined with their symbol,
|
||||
e.g. ``\```;
|
||||
e.g. `````;
|
||||
- ``raw``: the tag's content shall not be interpreted, which is generally
|
||||
only useful when the content is preprocessed (see below). The default
|
||||
is ``False`` if there is no preprocess method, and `True` otherwise;
|
||||
- ``noinline``: for raw tags, forbid inline tags (declared outside of the
|
||||
tag) within the tag (*only works with ``raw = True``*!);
|
||||
- ``generic``: the tag can be ended using the generic tag ending mark ``[/]``.
|
||||
It is defined as ``True`` by default for all tags;
|
||||
- ``notempty``: ignore the tag when its content is empty. By default, this
|
||||
value is `False`;
|
||||
- ``superblock``: is a super-block (for blocks), adds a paragraph
|
||||
tag implicitely.
|
||||
- ``superblock``: is a super-block (for blocks) which means it adds a block
|
||||
level, and adds a paragraph tag implicitely;
|
||||
- ``inlined``: if is a block, transforms automatically the surrounding block
|
||||
into a superblock while it's there;
|
||||
- ``procvalue``: process the value as normal text before passing it;
|
||||
- ``not_within_itself``: make that if a tag is opened within itself (depth
|
||||
included), the tag above and all tags below are closed first;
|
||||
- ``only_in``: allow the tag to only be beneath certain tags;
|
||||
- ``allowed_tags``: allowed tags right beneath the current one;
|
||||
- ``no_text``: disable raw text within the tag;
|
||||
- ``expect_child``: make that all content below (without depth) that isn't
|
||||
within the specified tags is ignored.
|
||||
|
||||
So for example, if I want to make the inline tag ``[hello]`` as an example,
|
||||
with the alternate name ``[hai]``, I'd start off by writing:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from .__base__ import *
|
||||
from textoutpc import InlineTag as _InlineTag
|
||||
|
||||
class TextoutHelloTag(TextoutInlineTag):
|
||||
class TextoutHelloTag(_InlineTag):
|
||||
""" The [hello] tag, which does things.
|
||||
Example uses:
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
Using textoutpc
|
||||
===============
|
||||
|
||||
To use this module, simply use the ``to<language>()`` functions once imported:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import textoutpc
|
||||
|
||||
text = "Hello, [i]beautiful [b]world[/i]!"
|
||||
print(textoutpc.tohtml(text))
|
||||
print("---")
|
||||
print(textoutpc.tolightscript(text))
|
||||
|
||||
The supported output types are:
|
||||
|
||||
- ``html``: `HTML`_ compatible output, requiring some additional style and
|
||||
script;
|
||||
- ``lightscript``: `Lightscript`_ Markdown-like language. See
|
||||
`the Lightscript topic on Planète Casio <Lightscript topic_>`_ for
|
||||
more information.
|
||||
|
||||
The ``tohtml()`` and ``tolightscript()`` can take additional keywords that
|
||||
tags can read so that they can adapt their behaviour. The name of the tweaks
|
||||
are case-insensitive and non-alphanumeric characters are ignored: for example,
|
||||
``label_prefix``, ``LABELPREFIX`` and ``__LaBeL___PRE_FIX__`` are all
|
||||
equivalent.
|
||||
|
||||
The following tweaks are read by the translator and built-in tags:
|
||||
|
||||
- ``inline``: if ``True``, only use inline tags, not blocks (for inline
|
||||
contexts such as instant messaging or one-line comments).
|
||||
- ``label_prefix`` (HTML): prefix to be used by the ``[label]`` and
|
||||
``[target]`` tags, e.g. ``msg45529-``. Defaults to `""` for PCv42
|
||||
compatibility;
|
||||
- ``obsolete_tags`` (HTML): use obsolete HTML tags for old browsers
|
||||
(e.g. lynx) compatibility, e.g. ``<b>``, ``<i>``, ``<center>``, and
|
||||
others. Defaults to ``True``.
|
||||
- ``title_level`` (HTML): level at which to start for titles and subtitles,
|
||||
e.g. ``h5`` for ``h5`` for titles and ``h6`` for subtitles.
|
||||
|
||||
An example call would be:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/env python3
|
||||
import textoutpc
|
||||
|
||||
print(textoutpc.tohtml("Hello, [i]beautiful[/i]!", obsolete__TAGS=False))
|
||||
|
||||
.. _HTML: https://www.w3.org/html/
|
||||
.. _Lightscript: https://git.planet-casio.com/lephe/lightscript
|
||||
.. _Lightscript topic: https://planet-casio.com/Fr/forums/lecture_sujet.php?id=15022
|
|
@ -1 +0,0 @@
|
|||
regex
|
|
@ -16,6 +16,12 @@ def parse_args():
|
|||
ap.add_argument('-o', dest='output', default=sys.stdout,
|
||||
type=lambda x: sys.stdout if x == '-' else open(x, 'w'),
|
||||
help='the output source, stdout by default.')
|
||||
ap.add_argument('--inline', dest='inline', action='store_true',
|
||||
help='only inline tags will be interpreted')
|
||||
ap.add_argument('--obsolete-tags', dest='obs_tags', action='store_true',
|
||||
help='whether to use obsolete HTML tags such as <b>')
|
||||
ap.add_argument('--label-prefix', dest='lbl_prefix',
|
||||
help='prefix to use for label tags, e.g. "msg55459-"')
|
||||
ap.add_argument('input', nargs='?', default=sys.stdin,
|
||||
type=lambda x: sys.stdin if x == '-' else open(x, 'r'),
|
||||
help='the input source, stdin by default.')
|
||||
|
@ -27,10 +33,14 @@ def main():
|
|||
""" Main function of the script. """
|
||||
|
||||
args = parse_args()
|
||||
print(textoutpc.tohtml(args.input.read()), file=args.output, end='')
|
||||
print(textoutpc.tohtml(args.input.read(), inline=args.inline,
|
||||
obsolete_tags=args.obs_tags, label_prefix=args.lbl_prefix),
|
||||
file=args.output, end='')
|
||||
|
||||
if __name__ == '__main__':
|
||||
# TODO: manage exceptions
|
||||
main()
|
||||
try:
|
||||
main()
|
||||
except e:
|
||||
print("textout2html: error: " + str(e))
|
||||
|
||||
# End of file.
|
||||
|
|
|
@ -30,7 +30,9 @@ def main():
|
|||
print(textoutpc.tolightscript(args.input.read()), file=args.output, end='')
|
||||
|
||||
if __name__ == '__main__':
|
||||
# TODO: manage exceptions
|
||||
main()
|
||||
try:
|
||||
main()
|
||||
except e:
|
||||
print("textout2ls: error: " + str(e))
|
||||
|
||||
# End of file.
|
48
setup.cfg
48
setup.cfg
|
@ -1,2 +1,50 @@
|
|||
[metadata]
|
||||
name = textoutpc
|
||||
version = attr: textoutpc.version
|
||||
url = https://textout.touhey.pro/
|
||||
project_urls =
|
||||
Documentation = https://textout.touhey.pro/docs/
|
||||
author = Thomas Touhey
|
||||
author_email = thomas@touhey.fr
|
||||
description = textout() equivalent from Planète Casio
|
||||
long_description = file: README.rst
|
||||
keywords = planète casio, textout, bbcode, translator, parser
|
||||
license = MIT
|
||||
classifiers =
|
||||
Development Status :: 2 - Pre-Alpha
|
||||
License :: OSI Approved :: MIT License
|
||||
Natural Language :: French
|
||||
Operating System :: OS Independent
|
||||
Programming Language :: Python :: 3
|
||||
Intended Audience :: Developers
|
||||
Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries
|
||||
Topic :: Text Processing :: Markup :: HTML
|
||||
|
||||
[options]
|
||||
zip_safe = False
|
||||
include_package_data = True
|
||||
packages = textoutpc, textoutpc.builtin
|
||||
test_suite = test
|
||||
scripts =
|
||||
scripts/textout2html
|
||||
scripts/textout2ls
|
||||
install_requires =
|
||||
regex
|
||||
thcolor
|
||||
|
||||
[options.package_data]
|
||||
* = *.txt, *.rst
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = docs
|
||||
|
||||
[wheel]
|
||||
universal = True
|
||||
|
||||
[flake8]
|
||||
ignore = F401, F403, E128, E131, E241, E261, E265, E271, W191
|
||||
exclude = .git, __pycache__, build, dist, docs/conf.py, test.py, test
|
||||
|
||||
[tool:pytest]
|
||||
python_files = tests.py test_*.py *_tests.py
|
||||
testpaths = tests
|
||||
|
|
35
setup.py
35
setup.py
|
@ -1,37 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the fingerd Python 3.x module, which is MIT-licensed.
|
||||
# This file is part of the textoutpc Python 3.x module, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Setup script for the textoutpc Python package and script. """
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import setup as _setup
|
||||
|
||||
setup(name='textoutpc',
|
||||
version='0.1',
|
||||
description='Textout() equivalent from Planète Casio',
|
||||
author='Thomas "Cakeisalie5" Touhey',
|
||||
author_email='thomas@touhey.fr',
|
||||
url='https://forge.touhey.fr/pc/textout.git/',
|
||||
license='MIT',
|
||||
keywords='planète casio textout bbcode translator parser',
|
||||
kwargs = {}
|
||||
|
||||
packages=find_packages(),
|
||||
scripts=['textout2html', 'textout2lightscript'],
|
||||
test_suite="test",
|
||||
try:
|
||||
from sphinx.setup_command import BuildDoc as _BuildDoc
|
||||
kwargs['cmdclass'] = {'build_sphinx': _BuildDoc}
|
||||
except:
|
||||
pass
|
||||
|
||||
install_requires=['regex'],
|
||||
# Actually, most of the project's data is read from the `setup.cfg` file.
|
||||
|
||||
classifiers = [
|
||||
'Development Status :: 2 - Pre-Alpha',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Natural Language :: French',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Intended Audience :: Developers',
|
||||
'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries',
|
||||
'Topic :: Text Processing :: Markup :: HTML'
|
||||
]
|
||||
)
|
||||
_setup(**kwargs)
|
||||
|
||||
# End of file.
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Unit tests for the Python version of textout.
|
||||
Uses the builtin `unittest` module.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import textoutpc
|
||||
|
||||
# Define the tests.
|
||||
|
||||
__test_cases = {
|
||||
# Basic text.
|
||||
'': '',
|
||||
'lol': '<p>lol</p>',
|
||||
'<script>alert(1);</script>': \
|
||||
'<p><script>alert(1);</script></p>',
|
||||
|
||||
# Other tests. (?)
|
||||
'[a][c][/a]': '<p>[a][c][/a]</p>',
|
||||
'[a][a]': '<p>[a][a]</p>',
|
||||
"[<>]><[/<>]": "<p>[<>]><[/<>]</p>",
|
||||
|
||||
# Autolinking.
|
||||
'(http://www.example.org/some-[damn-url]-(youknow))': \
|
||||
'<p>(<a href="http://www.example.org/some-[damn-url]-(youknow)">' \
|
||||
'http://www.example.org/some-[damn-url]-(youknow)</a>)</p>',
|
||||
'https://thomas.touhey.fr/, tu vois ?': \
|
||||
'<p><a href="https://thomas.touhey.fr/">https://thomas.touhey.fr/' \
|
||||
'</a>, tu vois ?</p>',
|
||||
|
||||
# Basic text styling.
|
||||
'[u][b][a][i][/b]': "<p><u><b>[a]<i></i></b></u></p>",
|
||||
'[u][b]a[/]mdr': '<p><u><b>a</b>mdr</u></p>',
|
||||
|
||||
# Blocks, alignment.
|
||||
'[left]': '',
|
||||
'[left]lol[/]hi': '<div class="align-left"><p>lol</p></div><p>hi</p>',
|
||||
'a[justify]b': '<p>a</p><div class="align-justify"><p>b</p></div>',
|
||||
'a[i][justify]b': '<p>a</p>' \
|
||||
'<div class="align-justify"><p><i>b</i></p></div>',
|
||||
'a[i]k[center]b': '<p>a<i>k</i></p>' \
|
||||
'<div class="align-center"><p><i>b</i></p></div>',
|
||||
'a[i]k[center][b]b[justify]c[/center]d[/]wouhou': \
|
||||
'<p>a<i>k</i></p>' \
|
||||
'<div class="align-center"><p><i><b>b</b></i></p></div>' \
|
||||
'<div class="align-justify"><p><i><b>c</b></i></p></div>' \
|
||||
'<p><i>d</i>wouhou</p>',
|
||||
|
||||
# Show tag for super preprocessing blocks.
|
||||
'[quote][show][justify]hehe': \
|
||||
'<div class="citation"><p><div class="align-justify">' \
|
||||
'<p>hehe</p></div></p></div>',
|
||||
|
||||
# Titles.
|
||||
'lolk[title]smth': '<p>lolk</p>' '<h4>smth</h4>',
|
||||
'[subtitle]<>': '<h5><></h5>',
|
||||
|
||||
# Fonts.
|
||||
'[arial]test': '<p><span style="font-family: arial">test</span></p>',
|
||||
'[font=mono]stereo': \
|
||||
'<p><span style="font-family: monospace">stereo</span></p>',
|
||||
'[haettenschweiler]': '',
|
||||
'[font=hello]yea': '<p>[font=hello]yea</p>',
|
||||
|
||||
# Color.
|
||||
'yea[color=blue]dabadee': \
|
||||
'<p>yea<span style="color: #0000FF">dabadee</span></p>',
|
||||
'[color=#12345F]a': '<p><span style="color: #12345F">a</span></p>',
|
||||
'[color=#123]a': '<p><span style="color: #112233">a</span></p>',
|
||||
'[color=123]a': '<p><span style="color: #010203">a</span></p>',
|
||||
'[color=chucknorris]a': '<p><span style="color: #C00000">a</span></p>',
|
||||
'[color=rgb(1, 22,242)]a': '<p><span style="color: #0116F2">a</span></p>',
|
||||
'[color= rgb (1,22, 242 , 50.0% )]a': '<p><span style="color: #0116F2; ' \
|
||||
'color: rgba(1, 22, 242, 0.5)">a</span></p>',
|
||||
'[color=rgba(1,22,242,0.500)]a': '<p><span style="color: #0116F2; ' \
|
||||
'color: rgba(1, 22, 242, 0.5)">a</span></p>',
|
||||
'[color=hsl(0, 1,50.0%)]r': '<p><span style="color: #FF0000">r</span></p>',
|
||||
# TODO: hls, hwb
|
||||
|
||||
# Links.
|
||||
'[url]': '<p>[url]</p>',
|
||||
'[url=https://thomas.touhey.fr/]mon profil est le meilleur[/url]':
|
||||
'<p><a href="https://thomas.touhey.fr/">mon profil est le meilleur' \
|
||||
'</a></p>',
|
||||
'[url=https://thomas.touhey.fr/]': \
|
||||
'<p><a href="https://thomas.touhey.fr/">https://thomas.touhey.fr/' \
|
||||
'</a></p>',
|
||||
'[url=http://hey.org/lol[]>"a]': '<p><a href="http://hey.org/lol[]>' \
|
||||
'"a">' \
|
||||
'http://hey.org/lol[]>"a</a></p>',
|
||||
'[url]javascript:alert(1)[/url]': '<p>[url]javascript:alert(1)[/url]</p>',
|
||||
'[url]<script>alert(1);</script>[/url]': \
|
||||
'<p>[url]<script>alert(1);</script>[/url]</p>',
|
||||
|
||||
'[profil]cake[/profil]': \
|
||||
'<p><a href="https://www.planet-casio.com/Fr/compte/voir_profil.php' \
|
||||
'?membre=cake">cake</a></p>',
|
||||
'[profile]ekac': \
|
||||
'<p><a href="https://www.planet-casio.com/Fr/compte/voir_profil.php' \
|
||||
'?membre=ekac">ekac</a></p>',
|
||||
|
||||
# Quotes.
|
||||
'[quote]': '',
|
||||
'[quote]a': \
|
||||
'<div class="citation"><p>a</p></div>',
|
||||
'[quote=Test 1 :)]lel[/quote]': \
|
||||
'<div class="citation"><p><b>Test 1 ' \
|
||||
'<img src="/images/smileys/smile.gif"> a écrit :</b></p><br />' \
|
||||
'lel</p></div>',
|
||||
|
||||
# Spoilers.
|
||||
'[spoiler]': '',
|
||||
'[spoiler=Hello|world> :D]Close this, quick![/spoiler]': \
|
||||
'<div class="spoiler"><div class="title on" ' \
|
||||
'onclick="toggleSpoiler(this.parentNode, ' "'open'" ');">Hello' \
|
||||
'</div><div class="title off" ' \
|
||||
'onclick="toggleSpoiler(this.parentNode, ' "'close'" ');">world' \
|
||||
'> <img src="/images/smileys/grin.gif"></div><div class="off">' \
|
||||
'Close this, quick!</div></div>',
|
||||
|
||||
# Code.
|
||||
'[code]': '',
|
||||
"`[code]`": '<p><span style="font-family: monospace;">[code]</span></p>',
|
||||
|
||||
'[inlinecode]': '',
|
||||
"[inlinecode]`[/inlinecode]": \
|
||||
'<p><span style="font-family: monospace;">`</span></p>',
|
||||
|
||||
"[b]a[noeval]b[/b]c[/noeval]d": "<p><b>ab[/b]cd</b></p>",
|
||||
"a[noeval]b[noeval]c[/noeval]d[/noeval]e": "<p>ab[noeval]c[/noeval]de</p>",
|
||||
"[noeval]``[/noeval]": "<p>``</p>",
|
||||
'[noeval]<>[/noeval]': '<p><></p>',
|
||||
|
||||
# Pictures.
|
||||
'[img]': '<p>[img]</p>',
|
||||
'[img]"incroyable<>"[/img]': \
|
||||
'<p>[img]"incroyable<>"[/img]</p>',
|
||||
|
||||
# Videos.
|
||||
'[video]"><script>alert(1)</script>[/video]': \
|
||||
'<p>[video]"><script>alert(1)</script>' \
|
||||
'[/video]</p>',
|
||||
'[video]<script>alert(document.cookie)</script>[/video]': \
|
||||
'<p>[video]<script>alert(document.cookie)</script>' \
|
||||
'[/video]</p>',
|
||||
'[video]https://www.youtube.com/watch?v=6odDOOyUawY[/video]': \
|
||||
'<div class="video-wrapper video-medium"><iframe ' \
|
||||
'src="https://www.youtube.com/embed/6odDOOyUawY" ' \
|
||||
'frameborder="0" allowfullscreen></iframe></div>',
|
||||
'[video]https://www.youtube.com/watch?v=<script>alert(1)</script>': \
|
||||
'<p><a href="https://www.youtube.com/watch?v=<script>alert(1)' \
|
||||
'</script>">' \
|
||||
'https://www.youtube.com/watch?v=<script>alert(1)' \
|
||||
'</script></a></p>',
|
||||
|
||||
# Progress bars.
|
||||
'[progress=lol]mdr[/progress]': '<p>[progress=lol]mdr[/progress]</p>',
|
||||
|
||||
# Text rotation obfuscation.
|
||||
'[rot13]obawbhe[/rot13]': '<p>bonjour</p>',
|
||||
}
|
||||
|
||||
# Define the tests wrapper, and define the classes.
|
||||
|
||||
_cnt = 0
|
||||
_len = len(str(len(__test_cases)))
|
||||
_templ = """\
|
||||
def test_html{n:0>{l}}(self):
|
||||
self.assertEqual({r}, textoutpc.tohtml({i}))
|
||||
"""
|
||||
|
||||
def _wrap_test(inp, res):
|
||||
global _cnt
|
||||
|
||||
_cnt += 1
|
||||
return _templ.format(n = _cnt, l = _len, i = repr(inp), r = repr(res))
|
||||
|
||||
exec("class TextoutHTMLTest(unittest.TestCase):\n maxDiff = None\n" + \
|
||||
'\n'.join(map(lambda args: _wrap_test(*args), __test_cases.items())),
|
||||
globals())
|
||||
|
||||
# If run as main script, run the test function.
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
# End of file.
|
|
@ -1,44 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Unit tests for the Python version of textout, lightscript-related funcs.
|
||||
Uses the builtin `unittest` module.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
import textoutpc
|
||||
|
||||
# Define the tests.
|
||||
|
||||
__test_cases = {
|
||||
# Basic text.
|
||||
'': '',
|
||||
}
|
||||
|
||||
# Define the tests wrapper, and define the classes.
|
||||
|
||||
_cnt = 0
|
||||
_len = len(str(len(__test_cases)))
|
||||
_templ = """\
|
||||
def test_lgsp{n:0>{l}}(self):
|
||||
self.assertEqual({r}, textoutpc.tolightscript({i}))
|
||||
"""
|
||||
|
||||
def _wrap_test(inp, res):
|
||||
global _cnt
|
||||
|
||||
_cnt += 1
|
||||
return _templ.format(n = _cnt, l = _len, i = repr(inp), r = repr(res))
|
||||
|
||||
exec("class TextoutLSTest(unittest.TestCase):\n maxDiff = None\n" + \
|
||||
'\n'.join(map(lambda args: _wrap_test(*args), __test_cases.items())),
|
||||
globals())
|
||||
|
||||
# If run as main script, run the test function.
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,230 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Unit tests for the Python version of textout. """
|
||||
|
||||
import pytest
|
||||
|
||||
from textoutpc import tohtml as _tohtml
|
||||
|
||||
# Define the tests.
|
||||
|
||||
@pytest.mark.parametrize('test_input,expected', (
|
||||
# Basic text.
|
||||
|
||||
('', ''),
|
||||
('lol', '<p>lol</p>'),
|
||||
('<script>alert(1);</script>',
|
||||
'<p><script>alert(1);</script></p>'),
|
||||
|
||||
# Other tests. (?)
|
||||
|
||||
('[a][c][/a]', '<p>[a][c][/a]</p>'),
|
||||
('[a][a]', '<p>[a][a]</p>'),
|
||||
("[<>]><[/<>]", "<p>[<>]><[/<>]</p>"),
|
||||
|
||||
# Autolinking.
|
||||
|
||||
('(http://www.example.org/some-[damn-url]-(youknow))',
|
||||
'<p>(<a href="http://www.example.org/some-[damn-url]-(youknow)">' \
|
||||
'http://www.example.org/some-[damn-url]-(youknow)</a>)</p>'),
|
||||
('https://thomas.touhey.fr/, tu vois ?',
|
||||
'<p><a href="https://thomas.touhey.fr/">https://thomas.touhey.fr/' \
|
||||
'</a>, tu vois ?</p>'),
|
||||
|
||||
# Basic text styling.
|
||||
|
||||
('[u][b][a][i][/b]', "<p><u><b>[a]</b></u></p>"),
|
||||
('[u][b]a[/]mdr', '<p><u><b>a</b>mdr</u></p>'),
|
||||
|
||||
# Blocks, alignment.
|
||||
|
||||
('[left]', ''),
|
||||
('[left]lol[/]hi', '<div class="align-left"><p>lol</p></div><p>hi</p>'),
|
||||
('a[justify]b', '<p>a</p><div class="align-justify"><p>b</p></div>'),
|
||||
('a[i]', '<p>a</p>'),
|
||||
('a[i][justify]b', '<p>a</p>' \
|
||||
'<div class="align-justify"><p><i>b</i></p></div>'),
|
||||
('a[i]k[center]b', '<p>a<i>k</i></p>' \
|
||||
'<div class="align-center"><p><i>b</i></p></div>'),
|
||||
('a[i]k[center][b]b[justify]c[/center]d[/]wouhou',
|
||||
'<p>a<i>k</i></p>' \
|
||||
'<div class="align-center"><p><i><b>b</b></i></p>' \
|
||||
'<div class="align-justify"><p><i><b>c</b></i></p></div></div>' \
|
||||
'<p><i>d</i>wouhou</p>'),
|
||||
|
||||
# Show tag for super preprocessing blocks.
|
||||
|
||||
('[show]lol', '<p><span class="inline-code">lol</span></p>'),
|
||||
('[quote][show][justify]hehe',
|
||||
'<div class="citation"><p><span class="inline-code">' \
|
||||
'<div class="align-justify">' \
|
||||
'<p>hehe</p></div>' \
|
||||
'</span></p></div>'),
|
||||
|
||||
# Titles.
|
||||
|
||||
('lolk[title]smth', '<p>lolk</p>' '<h1 class="title">smth</h1>'),
|
||||
('[subtitle]<>', '<h2 class="subtitle"><></h2>'),
|
||||
|
||||
# Fonts.
|
||||
|
||||
('[arial]test', '<p><span style="font-family: arial">test</span></p>'),
|
||||
('[font=mono]stereo',
|
||||
'<p><span style="font-family: monospace">stereo</span></p>'),
|
||||
('[haettenschweiler]', ''),
|
||||
('[font=hello]yea', '<p>[font=hello]yea</p>'),
|
||||
|
||||
# Color.
|
||||
|
||||
('yea[color=blue]dabadee', \
|
||||
'<p>yea<span style="color: #0000FF">dabadee</span></p>'),
|
||||
('[color=#12345F]a', '<p><span style="color: #12345F">a</span></p>'),
|
||||
('[color=#123]a', '<p><span style="color: #112233">a</span></p>'),
|
||||
('[color=123]a', '<p><span style="color: #010203">a</span></p>'),
|
||||
('[color=chucknorris]a', '<p><span style="color: #C00000">a</span></p>'),
|
||||
('[color=rgb(1, 22,242)]a',
|
||||
'<p><span style="color: #0116F2">a</span></p>'),
|
||||
('[color= rgb (1,22, 242 , 50.0% )]a',
|
||||
'<p><span style="color: #0116F2; ' \
|
||||
'color: rgba(1, 22, 242, 0.5)">a</span></p>'),
|
||||
('[color=rgba(1,22,242,0.500)]a', '<p><span style="color: #0116F2; ' \
|
||||
'color: rgba(1, 22, 242, 0.5)">a</span></p>'),
|
||||
('[color=rbga(5, 7)]b', '<p><span style="color: #050007">b</span></p>'),
|
||||
('[color=hsl(0, 1,50.0%)]r',
|
||||
'<p><span style="color: #FF0000">r</span></p>'),
|
||||
# TODO: hls, hwb
|
||||
|
||||
# Links.
|
||||
|
||||
('[url]', '<p>[url]</p>'),
|
||||
('[url=https://thomas.touhey.fr/]mon profil est le meilleur[/url]',
|
||||
'<p><a href="https://thomas.touhey.fr/">mon profil est le meilleur' \
|
||||
'</a></p>'),
|
||||
('[url=https://thomas.touhey.fr/]',
|
||||
'<p><a href="https://thomas.touhey.fr/">https://thomas.touhey.fr/' \
|
||||
'</a></p>'),
|
||||
('[url=http://hey.org/lol[]>"a]', '<p><a href="http://hey.org/lol[]>' \
|
||||
'"a">' 'http://hey.org/lol[]>"a</a></p>'),
|
||||
('[url]javascript:alert(1)[/url]',
|
||||
'<p>[url]javascript:alert(1)[/url]</p>'),
|
||||
('[url]<script>alert(1);</script>[/url]',
|
||||
'<p>[url]<script>alert(1);</script>[/url]</p>'),
|
||||
|
||||
('[profil]cake[/profil]',
|
||||
'<p><a href="https://www.planet-casio.com/Fr/compte/voir_profil.php' \
|
||||
'?membre=cake">cake</a></p>'),
|
||||
('[profile]ekac',
|
||||
'<p><a href="https://www.planet-casio.com/Fr/compte/voir_profil.php' \
|
||||
'?membre=ekac">ekac</a></p>'),
|
||||
|
||||
# Quotes.
|
||||
|
||||
('[quote]', ''),
|
||||
('[quote]a',
|
||||
'<div class="citation"><p>a</p></div>'),
|
||||
('[quote=Test 1 :)]lel[/quote]',
|
||||
'<div class="citation"><p><b>Test 1 ' \
|
||||
'<img src="/images/smileys/smile.gif"> a écrit :</b></p><p>' \
|
||||
'lel</p></div>'),
|
||||
|
||||
# Spoilers.
|
||||
|
||||
('[spoiler]', ''),
|
||||
('[spoiler=Hello|world> :D]Close this, quick![/spoiler]',
|
||||
'<div class="spoiler"><div class="title on" ' \
|
||||
'onclick="toggleSpoiler(this.parentNode, ' "'open'" ');"><p>Hello' \
|
||||
'</p></div><div class="title off" ' \
|
||||
'onclick="toggleSpoiler(this.parentNode, ' "'close'" ');"><p>world' \
|
||||
'> <img src="/images/smileys/grin.gif"></p></div>' \
|
||||
'<div class="off"><p>Close this, quick!</p></div></div>'),
|
||||
|
||||
# Code.
|
||||
|
||||
('[code]', ''),
|
||||
("`[code]`", '<p><span class="inline-code">[code]</span></p>'),
|
||||
|
||||
('[inlinecode]', ''),
|
||||
("[inlinecode]`[/inlinecode]",
|
||||
'<p><span class="inline-code">`</span></p>'),
|
||||
|
||||
("[b]a[noeval]b[/b]c[/noeval]d", "<p><b>ab[/b]cd</b></p>"),
|
||||
("a[noeval]b[noeval]c[/noeval]d[/noeval]e",
|
||||
"<p>ab[noeval]c[/noeval]de</p>"),
|
||||
("[noeval]``[/noeval]", "<p>``</p>"),
|
||||
('[noeval]<>[/noeval]', '<p><></p>'),
|
||||
|
||||
# Pictures.
|
||||
|
||||
('[img]', '<p>[img]</p>'),
|
||||
('[img]"incroyable<>"[/img]',
|
||||
'<p>[img]"incroyable<>"[/img]</p>'),
|
||||
('[img=right|float|12x345]https://example.org/image.png',
|
||||
'<img src="https://example.org/image.png" class="img-float-right" ' \
|
||||
'style="width: 12px; height: 345px" />'),
|
||||
|
||||
# Videos.
|
||||
|
||||
('[video]"><script>alert(1)</script>[/video]',
|
||||
'<p>[video]"><script>alert(1)</script>' \
|
||||
'[/video]</p>'),
|
||||
('[video]<script>alert(document.cookie)</script>[/video]',
|
||||
'<p>[video]<script>alert(document.cookie)</script>' \
|
||||
'[/video]</p>'),
|
||||
('[video]https://www.youtube.com/watch?v=6odDOOyUawY[/video]',
|
||||
'<div class="video-wrapper" style="padding-bottom: 56.25%"><iframe ' \
|
||||
'src="https://www.youtube.com/embed/6odDOOyUawY" ' \
|
||||
'frameborder="0" allowfullscreen></iframe></div>'),
|
||||
('[video]https://www.youtube.com/watch?v=<script>alert(1)</script>',
|
||||
'<p><a href="https://www.youtube.com/watch?v=<script>alert(1)' \
|
||||
'</script>">' \
|
||||
'https://www.youtube.com/watch?v=<script>alert(1)' \
|
||||
'</script></a></p>'),
|
||||
('[video=left|float|4:3]https://www.youtube.com/watch?v=XEjLoHdbVeE',
|
||||
'<div class="video-wrapper img-float-left" ' \
|
||||
'style="padding-bottom: 75%"><iframe ' \
|
||||
'src="https://www.youtube.com/embed/XEjLoHdbVeE" frameborder="0" ' \
|
||||
'allowfullscreen></iframe></div>'),
|
||||
('lol[youtube]h4WLX8hfpJw', '<p>lol</p><div class="video-wrapper" ' \
|
||||
'style="padding-bottom: 56.25%"><iframe ' \
|
||||
'src="https://www.youtube.com/embed/h4WLX8hfpJw" frameborder="0" ' \
|
||||
'allowfullscreen></iframe></div>'),
|
||||
|
||||
('[color=blue][youtube]h4WLX8hfpJw',
|
||||
'<div class="video-wrapper" style="padding-bottom: 56.25%">' \
|
||||
'<iframe src="https://www.youtube.com/embed/h4WLX8hfpJw" ' \
|
||||
'frameborder="0" allowfullscreen></iframe></div>'),
|
||||
('[color=blue]oh[youtube]h4WLX8hfpJw',
|
||||
'<p><span style="color: #0000FF">oh</span></p>' \
|
||||
'<div class="video-wrapper" style="padding-bottom: 56.25%"><iframe ' \
|
||||
'src="https://www.youtube.com/embed/h4WLX8hfpJw" frameborder="0" ' \
|
||||
'allowfullscreen></iframe></div>'),
|
||||
|
||||
# Progress bars.
|
||||
|
||||
('[progress=lol]mdr[/progress]', '<p>[progress=lol]mdr[/progress]</p>'),
|
||||
|
||||
# Text rotation obfuscation.
|
||||
|
||||
('[rot13]obawbhe[/rot13]', '<p>bonjour</p>'),
|
||||
|
||||
# Lists.
|
||||
|
||||
('[list]haha[b][*]wow[*]incredible[/b][/*]wow[*]yuy[/list]',
|
||||
'<ul><li><p>wow</p></li><li><p>incredible[/b]</p></li>' \
|
||||
'<li><p>yuy</p></li></ul>'),
|
||||
('[list]\n[*]bonjour', '<ul><li><p>bonjour</p></li></ul>'),
|
||||
|
||||
# Smileys.
|
||||
|
||||
(':)', '<p><img src="/images/smileys/smile.gif"></p>'),
|
||||
(':):)', '<p>:):)</p>'),
|
||||
(':) :D', '<p><img src="/images/smileys/smile.gif"> ' \
|
||||
'<img src="/images/smileys/grin.gif"></p>'),
|
||||
))
|
||||
def test_html(test_input, expected):
|
||||
assert _tohtml(test_input) == expected
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Unit tests for the Python version of textout. """
|
||||
|
||||
import pytest
|
||||
|
||||
from textoutpc import tohtml as _tohtml
|
||||
|
||||
@pytest.mark.parametrize('test_input,expected', (
|
||||
# Basic text.
|
||||
|
||||
('', ''),
|
||||
('lol', 'lol'),
|
||||
|
||||
# Basic text styling.
|
||||
|
||||
('[u][b]a[/]mdr', '<u><b>a</b>mdr</u>'),
|
||||
|
||||
# Links.
|
||||
|
||||
('[url=https://thomas.touhey.fr/]',
|
||||
'<a href="https://thomas.touhey.fr/">https://thomas.touhey.fr/</a>'),
|
||||
))
|
||||
def test_htmli(test_input, expected):
|
||||
assert _tohtml(test_input, inline = True) == expected
|
||||
|
||||
# End of file.
|
|
@ -3,15 +3,19 @@
|
|||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" HTML/CSS-like color parsing, mainly for the `[color]` tag.
|
||||
Defines the `get_color()` function which returns an rgba value.
|
||||
""" Unit tests for the Python version of textout, lightscript-related
|
||||
functions. """
|
||||
|
||||
The functions in this module do not aim at being totally compliant with
|
||||
the W3C standards, although it is inspired from it.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from .read import get_color
|
||||
from textoutpc import tolightscript as _tolightscript
|
||||
|
||||
__all__ = ["get_color"]
|
||||
@pytest.mark.parametrize('test_input,expected', (
|
||||
# Basic text.
|
||||
|
||||
('', ''),
|
||||
))
|
||||
def test_lightscript(test_input, expected):
|
||||
assert _tolightscript(test_input) == expected
|
||||
|
||||
# End of file.
|
|
@ -7,27 +7,41 @@
|
|||
Really simplifies the thing.
|
||||
"""
|
||||
|
||||
import io as _io
|
||||
from .translate import Translator as _Translator
|
||||
from io import StringIO as _StringIO
|
||||
|
||||
__all__ = ["version", "tohtml", "tolightscript"]
|
||||
from ._options import TextoutOptions as Options, \
|
||||
TextoutBlockTag as BlockTag, TextoutInlineTag as InlineTag, \
|
||||
TextoutParagraphTag as ParagraphTag, TextoutSmiley as Smiley, \
|
||||
TextoutImage as Image, TextoutVideo as Video
|
||||
from ._translate import Translator as _Translator
|
||||
|
||||
version = "0.1"
|
||||
__all__ = ["version", "tohtml", "tolightscript",
|
||||
"Options", "BlockTag", "ParagraphTag", "InlineTag",
|
||||
"Smiley", "Image", "Video"]
|
||||
|
||||
def tohtml(message, **tweaks):
|
||||
version = "0.2.1"
|
||||
|
||||
# ---
|
||||
# Public functions.
|
||||
# ---
|
||||
|
||||
_default_options = Options()
|
||||
|
||||
def tohtml(message, options = _default_options, **tweaks):
|
||||
""" Converts textout BBcode to HTML.
|
||||
Receives a string, returns a string. """
|
||||
|
||||
return _Translator(_io.StringIO(message), _io.StringIO(), 'html', \
|
||||
tweaks).process().getvalue()
|
||||
t = _Translator(_StringIO(message), _StringIO(), 'html', \
|
||||
tweaks, options)
|
||||
return t.process().getvalue()
|
||||
|
||||
def tolightscript(message, **tweaks):
|
||||
def tolightscript(message, options = _default_options, **tweaks):
|
||||
""" Converts textout BBcode to Lightscript.
|
||||
Receives a string, returns a string. """
|
||||
|
||||
return "" # TODO: real thing one day
|
||||
|
||||
return _Translator(_io.StringIO(message), _io.StringIO(), 'lightscript', \
|
||||
tweaks).process().getvalue()
|
||||
return _Translator(_StringIO(message), _StringIO(), 'lightscript', \
|
||||
tweaks, options).process().getvalue()
|
||||
|
||||
# End of file.
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Utilities for HTML conversions. """
|
||||
|
||||
import regex as _re
|
||||
from html import escape
|
||||
|
||||
__all__ = ["escape", "urls", "SmileyConvertor"]
|
||||
|
||||
# ---
|
||||
# Smileys.
|
||||
# ---
|
||||
|
||||
class SmileyConvertor:
|
||||
""" Smileys convertor. """
|
||||
|
||||
def __init__(self, smileys = {}):
|
||||
self._html = {escape(a): b.url \
|
||||
for a, b in smileys.items() if b.url != None}
|
||||
self._re = _re.compile('(^|\\s)(' + '|'.join(map(_re.escape,
|
||||
self._html.keys())) + ')(\\s|$)')
|
||||
|
||||
def convert(self, text):
|
||||
cv = ""
|
||||
|
||||
while text:
|
||||
try:
|
||||
m = next(self._re.finditer(text))
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
cv += text[:m.start()] + m.group(1)
|
||||
cv += '<img src="' + self._html[m.group(2)] + '">'
|
||||
text = m.group(3) + text[m.end():]
|
||||
|
||||
return cv + text
|
||||
|
||||
# ---
|
||||
# URLs.
|
||||
# ---
|
||||
|
||||
_urlreg = _re.compile("""\
|
||||
(?P<sp>^|\s|[[:punct:]])
|
||||
(?P<url>(https?|ftp):
|
||||
(?P<ucore>[^\[\]\(\)\s]* (\[(?&ucore)\]?)* (\((?&ucore)\)?)*)*
|
||||
)
|
||||
""", _re.VERBOSE | _re.M)
|
||||
|
||||
def urls(text):
|
||||
""" Convert URLs. """
|
||||
|
||||
def _sub_html(m):
|
||||
sp = m.group('sp')
|
||||
url = m.group('url')
|
||||
aft = ''
|
||||
|
||||
# Hack for the last comma.
|
||||
if url[-1] == ',':
|
||||
url, aft = url[:-1], ','
|
||||
|
||||
text = '{}<a href="{}">{}</a>{}' \
|
||||
.format(sp, url, url, aft)
|
||||
return text
|
||||
|
||||
return _urlreg.sub(_sub_html, text)
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Utilities for Lightscript conversions. """
|
||||
|
||||
import regex as _re
|
||||
|
||||
__all__ = ["urls"]
|
||||
|
||||
# ---
|
||||
# URLs.
|
||||
# ---
|
||||
|
||||
_urlreg = _re.compile("""\
|
||||
(?P<sp>^|\s|[[:punct:]])
|
||||
(?P<url>(https?|ftp):
|
||||
(?P<ucore>[^\[\]\(\)\s]* (\[(?&ucore)\]?)* (\((?&ucore)\)?)*)*
|
||||
)
|
||||
""", _re.VERBOSE | _re.M)
|
||||
|
||||
def urls(text):
|
||||
""" Convert URLs. """
|
||||
|
||||
def _sub_ls(m):
|
||||
sp = m.group('sp')
|
||||
url = m.group('url')
|
||||
aft = ''
|
||||
|
||||
# Hack for the last comma.
|
||||
if url[-1] == ',':
|
||||
url, aft = url[:-1], ','
|
||||
|
||||
url = url.replace('<', '%3C')
|
||||
url = url.replace('>', '%3E')
|
||||
text = '{}<{}>{}'.format(sp, url, aft)
|
||||
return text
|
||||
|
||||
return _regurl.sub(_sub_ls, text)
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,355 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Base classes to use with options (tags, smileys) in textoutpc, with a
|
||||
manager class.
|
||||
|
||||
For your tag to be used as a textoutpc tag, you have to make it
|
||||
inherit one of the `TextoutBlockTag` or `TextoutInlineTag` classes.
|
||||
|
||||
Making separate tag modules is possible through the manager class,
|
||||
which allows not to hardcode the tags into the module. """
|
||||
|
||||
from functools import partial as _p
|
||||
from inspect import ismodule as _ismod, isclass as _isclass, \
|
||||
getargspec as _getargspec, getfullargspec as _getfullargspec, \
|
||||
currentframe as _currentframe, getouterframes as _getouterframes
|
||||
from importlib import import_module as _importmod
|
||||
|
||||
from ._html import SmileyConvertor as _htmlsm
|
||||
|
||||
__all__ = ["TextoutOptions",
|
||||
"TextoutTag", "TextoutBlockTag", "TextoutInlineTag", "TextoutParagraphTag",
|
||||
"TextoutSmiley", "TextoutImage", "TextoutVideo"]
|
||||
|
||||
def _getargscount(func):
|
||||
try:
|
||||
return len(_getfullargspec(func).args)
|
||||
except:
|
||||
return len(_getargspec(func).args)
|
||||
|
||||
# ---
|
||||
# Tags.
|
||||
# ---
|
||||
|
||||
# Main base tag class.
|
||||
# For more about defining a tag, see `doc/tags.md`.
|
||||
|
||||
class TextoutTag:
|
||||
""" The textout tag base class.
|
||||
Is initialized with these values:
|
||||
|
||||
<name><content><name>
|
||||
| name: "<name>" (only special chars such as `)
|
||||
| value: None
|
||||
[<name>]<content>[/<name>]
|
||||
| name: "[<name>]"
|
||||
| value: None
|
||||
[<name>]<content>[/] (when possible)
|
||||
| name: "[<name>]"
|
||||
| value: None
|
||||
[<name>=<value>]<content>[/<name>]
|
||||
| name: "[<name>]"
|
||||
| value: "<value>"
|
||||
[<name>=<value>]<content>[/] (when possible)
|
||||
| name: "[<name>]"
|
||||
| value: "<value>" """
|
||||
|
||||
aliases = ()
|
||||
|
||||
def __init__(self, name, value, ot, tweaks, options):
|
||||
""" Initialize the textout tag with the documented members. """
|
||||
|
||||
# Store internal data.
|
||||
|
||||
self.__name = name
|
||||
self.__value = value
|
||||
self.__output_type = ot
|
||||
self.__tweaks = tweaks
|
||||
self.__options = options
|
||||
|
||||
self.output_type = ot
|
||||
|
||||
# Call both prepare functions.
|
||||
|
||||
if hasattr(self, 'prepare'):
|
||||
try:
|
||||
assert _getargscount(self.prepare) == 4
|
||||
args = (name, value, ot)
|
||||
except:
|
||||
args = (name, value)
|
||||
self.prepare(*args)
|
||||
if hasattr(self, 'prepare_' + ot):
|
||||
prep = getattr(self, 'prepare_' + ot)
|
||||
try:
|
||||
assert len(_getargspec(prep).args) == 4
|
||||
args = (name, value, ot)
|
||||
except:
|
||||
args = (name, value)
|
||||
prep(*args)
|
||||
|
||||
# Prepare the preprocessing elements.
|
||||
|
||||
if hasattr(self, 'preprocess'):
|
||||
if hasattr(self, 'preprocess_' + ot):
|
||||
self.__preprocess0 = self.preprocess
|
||||
self.preprocess = self.__preprocess_double
|
||||
elif hasattr(self, 'preprocess_' + ot):
|
||||
self.preprocess = getattr(self, 'preprocess_' + ot)
|
||||
|
||||
if hasattr(self, 'preprocess'):
|
||||
self.__preprocess2 = self.preprocess
|
||||
self.preprocess = self.__preprocess_and_prepare
|
||||
else:
|
||||
self.__after_preprocess()
|
||||
|
||||
if hasattr(self, 'default_' + ot):
|
||||
self.default = getattr(self, 'default_' + ot)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}(name = {repr(self.__name)}, " \
|
||||
f"value = {repr(self.__value)}, " \
|
||||
f"ot = {repr(self.__output_type)})"
|
||||
|
||||
def __preprocess_double(self, content):
|
||||
""" Preprocess using the two methods. """
|
||||
|
||||
ct = self.__preprocess0(content)
|
||||
if ct != None:
|
||||
content = ct
|
||||
del ct
|
||||
|
||||
ct = self.__preprocess1(content)
|
||||
if ct != None:
|
||||
content = ct
|
||||
del ct
|
||||
|
||||
return content
|
||||
|
||||
def __preprocess_and_prepare(self, content):
|
||||
""" Preprocess and do the things after. """
|
||||
|
||||
ret = self.__preprocess2(content)
|
||||
self.__after_preprocess()
|
||||
return ret
|
||||
|
||||
def __out(self, name):
|
||||
""" Generic function to call two output functions of the same
|
||||
type. """
|
||||
|
||||
getattr(self, '__' + name)()
|
||||
getattr(self, name + '_' + self.__output_type)()
|
||||
|
||||
def __after_preprocess(self):
|
||||
""" After preprocessing, check the begin, content and end that may
|
||||
have been set by the preprocessing function. """
|
||||
|
||||
ot = self.__output_type
|
||||
|
||||
for otype in ('begin', 'content', 'end'):
|
||||
if hasattr(self, otype):
|
||||
if hasattr(self, otype + '_' + ot):
|
||||
setattr(self, '__' + otype, getattr(self, otype))
|
||||
setattr(self, otype, _p(self.__out, otype))
|
||||
elif hasattr(self, otype + '_' + ot):
|
||||
setattr(self, otype, getattr(self, otype + '_' + ot))
|
||||
|
||||
def tweak(self, key, default = None):
|
||||
try:
|
||||
return self.__tweaks[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def image(self, *args, **kwargs):
|
||||
return self.__options.get_image(*args, **kwargs)
|
||||
|
||||
def video(self, *args, **kwargs):
|
||||
return self.__options.get_video(*args, **kwargs)
|
||||
|
||||
# Role-specific base tag classes.
|
||||
|
||||
class TextoutBlockTag(TextoutTag):
|
||||
pass
|
||||
class TextoutInlineTag(TextoutTag):
|
||||
pass
|
||||
|
||||
# Default tag: paragraph.
|
||||
|
||||
class TextoutParagraphTag(TextoutBlockTag):
|
||||
""" Main tag for basic paragraphs. """
|
||||
|
||||
notempty = True
|
||||
|
||||
def begin_html(self):
|
||||
return '<p>'
|
||||
|
||||
def end_html(self):
|
||||
return '</p>'
|
||||
|
||||
# ---
|
||||
# Smileys.
|
||||
# ---
|
||||
|
||||
class TextoutSmiley:
|
||||
""" Base class for smileys. """
|
||||
|
||||
aliases = ()
|
||||
url = None
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}(aliases = {repr(self.aliases)}, " \
|
||||
f"url = {repr(self.url)})"
|
||||
|
||||
# ---
|
||||
# Multimedia.
|
||||
# ---
|
||||
|
||||
class TextoutImage:
|
||||
""" Base class for images. """
|
||||
|
||||
def __init__(self, url):
|
||||
raise ValueError("no URL supported")
|
||||
|
||||
class TextoutVideo:
|
||||
""" Base class for videos. """
|
||||
|
||||
def __init__(self, url):
|
||||
raise ValueError("no URL supported")
|
||||
|
||||
# ---
|
||||
# Options extractor and manager.
|
||||
# ---
|
||||
|
||||
_builtin_module = None
|
||||
def _get_builtin_module():
|
||||
""" Get the `.builtin` module. """
|
||||
|
||||
global _builtin_module
|
||||
|
||||
if _builtin_module == None:
|
||||
_builtin_module = _importmod('..builtin', __name__)
|
||||
return _builtin_module
|
||||
|
||||
class TextoutOptions:
|
||||
""" Options manager.
|
||||
Object responsible for getting the tags. """
|
||||
|
||||
def __init__(self, *modules, default = True):
|
||||
self._aliases = {}
|
||||
self._s_aliases = {}
|
||||
self._videos = []
|
||||
self._images = []
|
||||
|
||||
if default:
|
||||
self.add(_get_builtin_module())
|
||||
for mod in modules:
|
||||
self.add(mod)
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}()"
|
||||
|
||||
def add(self, element):
|
||||
""" Add an option. """
|
||||
|
||||
if isinstance(element, str):
|
||||
element = str(element)
|
||||
element = _importmod(element,
|
||||
_getouterframes(_currentframe(), 1)[0].name)
|
||||
|
||||
if _ismod(element):
|
||||
self.__extract(element)
|
||||
return True
|
||||
|
||||
if _isclass(element) and issubclass(element, TextoutTag):
|
||||
for alias in element.aliases:
|
||||
self._aliases[alias] = element
|
||||
return True
|
||||
|
||||
if _isclass(element) and issubclass(element, TextoutSmiley):
|
||||
for alias in element.aliases:
|
||||
self._s_aliases[alias] = element
|
||||
|
||||
self._htmlsm = None
|
||||
return True
|
||||
|
||||
if _isclass(element) and issubclass(element, TextoutImage):
|
||||
if not any(image is element for image in self._images):
|
||||
self._images.append(element)
|
||||
|
||||
if _isclass(element) and issubclass(element, TextoutVideo):
|
||||
if not any(video is element for video in self._videos):
|
||||
self._videos.append(element)
|
||||
|
||||
def __extract(self, module):
|
||||
""" Extract options from a module. """
|
||||
|
||||
tags = []
|
||||
smileys = []
|
||||
|
||||
# Obtain the list of properties from the module.
|
||||
|
||||
try:
|
||||
ds = module.__all__
|
||||
except:
|
||||
ds = dir(module)
|
||||
|
||||
# Get the submodules from the module (usually different files in the
|
||||
# tags module folder).
|
||||
|
||||
for submodule in (obj for name, obj in ((nm, getattr(module, nm)) \
|
||||
for nm in ds) if (name == '__init__' or name[0] != '_') \
|
||||
and _ismod(obj)):
|
||||
self.__extract(submodule)
|
||||
|
||||
# Extract the tags from the current module.
|
||||
|
||||
for obj in (obj for name, obj in ((nm, getattr(module, nm)) \
|
||||
for nm in ds) if name[0] != '_'):
|
||||
self.add(obj)
|
||||
|
||||
def get_smileys(self):
|
||||
""" Get the smileys dictionary. """
|
||||
|
||||
return self._s_aliases.copy()
|
||||
|
||||
def htmlsmileys(self, text):
|
||||
""" Get the smileys convertor for HTML. """
|
||||
|
||||
if not self._htmlsm:
|
||||
self._htmlsm = _htmlsm(self._s_aliases)
|
||||
return self._htmlsm.convert(text)
|
||||
|
||||
def get_video(self, url):
|
||||
""" Get a video using its URL. """
|
||||
|
||||
for video in self._videos:
|
||||
try:
|
||||
v = video(url)
|
||||
except:
|
||||
continue
|
||||
break
|
||||
else:
|
||||
raise ValueError("invalid video URL")
|
||||
return v
|
||||
|
||||
def get_image(self, url):
|
||||
""" Get an image using its URL. """
|
||||
|
||||
for image in self._images:
|
||||
try:
|
||||
i = image(url)
|
||||
except:
|
||||
continue
|
||||
break
|
||||
else:
|
||||
raise ValueError("invalid image URL")
|
||||
return i
|
||||
|
||||
def get_tag(self, name):
|
||||
""" Get the tag class corresponding to a name. """
|
||||
|
||||
return self._aliases[name]
|
||||
|
||||
# End of file.
|
|
@ -27,6 +27,7 @@ class TextoutUnit:
|
|||
PARSEP = 5
|
||||
|
||||
def __init__(self, *args):
|
||||
self.full = ''
|
||||
if len(args) > 1:
|
||||
self.type, self.name, self.value, *_ = args + (None,)
|
||||
return
|
||||
|
@ -66,10 +67,10 @@ class TextoutUnit:
|
|||
def __repr__(self):
|
||||
typetab = {self.BEGIN: "begin", self.END: "end",
|
||||
self.SPECIAL: "special", self.NEWLINE: "newline"}
|
||||
return '<TextoutUnit type={}{}{}>'.format(\
|
||||
return '_TextoutUnit(type={}{}{})'.format(\
|
||||
typetab[self.type],
|
||||
' name=' + self.name if self.name != None else "",
|
||||
' value="{}"'.format(self.value) if self.value != None else "")
|
||||
', name=' + repr(self.name) if self.name != None else "",
|
||||
', value=' + repr(self.value) if self.value != None else "")
|
||||
|
||||
def __equ__(self, other):
|
||||
if not isinstance(other, TextoutUnit):
|
||||
|
@ -100,22 +101,24 @@ class TextoutStream:
|
|||
#
|
||||
# FIXME: check the sizes? it seems that it stopped working…
|
||||
|
||||
_Tag = _re.compile("""\
|
||||
\[\s?(?P<bname>
|
||||
_Tag = _re.compile(r"""
|
||||
\[\s?
|
||||
(?P<bname>
|
||||
(?P<bname_e>[^\/\[\]\=][^\[\]\=]* (\[(?&bname_e)\]?)*)*
|
||||
)
|
||||
(\s?=\s?(?P<value>
|
||||
(?P<value_e>[^\[\]]* (\[(?&value_e)\]?)*)*
|
||||
))?\s?\]
|
||||
))?
|
||||
\s?\]
|
||||
|
|
||||
\[[\\/]\s?(?P<ename>
|
||||
\[[\\\/]\s?(?P<ename>
|
||||
(?P<ename_e>[^\/\[\]\=][^\[\]\=]* (\[(?&ename_e)\]?)*)*
|
||||
)\s?\]
|
||||
|
|
||||
(?P<parsep>[\n]{2,})
|
||||
|
|
||||
(?P<sname>`|[\n])
|
||||
""", _re.VERBOSE | _re.M)
|
||||
""", _re.VERBOSE | _re.DOTALL | _re.MULTILINE)
|
||||
|
||||
# Keep this buffer size above the maximum size of a tag (387)
|
||||
# for this class to work alright. Anything above 512 should work great.
|
||||
|
@ -125,18 +128,22 @@ class TextoutStream:
|
|||
def __init__(self, stream):
|
||||
# If the 'stream' is a string, we want to use standard stream
|
||||
# functions, so we're gonna enforce them using the `StringIO` class.
|
||||
|
||||
if isinstance(stream, str):
|
||||
stream = _io.StringIO(stream)
|
||||
|
||||
# Buffer management.
|
||||
|
||||
self.stream = stream
|
||||
self.buf = ""
|
||||
|
||||
# Management of the last tag match.
|
||||
|
||||
self.result = None
|
||||
self.last = None
|
||||
|
||||
# Error position.
|
||||
|
||||
self.pos = 0
|
||||
self.line = 0
|
||||
self.col = 0
|
||||
|
@ -144,22 +151,26 @@ class TextoutStream:
|
|||
def __iter__(self):
|
||||
# This class is (obviously) iterable.
|
||||
# We want to use this class as the iterator as well.
|
||||
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
# If we have a result, process it.
|
||||
|
||||
if self.result:
|
||||
data, self.result = TextoutUnit(self.result), None
|
||||
self.last = data
|
||||
return data
|
||||
|
||||
# Make sure to have enough data to read.
|
||||
|
||||
self.buf += self.stream.read(self.BUFFER_SIZE - len(self.buf))
|
||||
if not self.buf:
|
||||
self.last = None
|
||||
raise StopIteration
|
||||
|
||||
# Check that we have a result.
|
||||
|
||||
result = self._Tag.search(self.buf, partial = True)
|
||||
if not result:
|
||||
text = self.buf
|
||||
|
@ -169,6 +180,7 @@ class TextoutStream:
|
|||
|
||||
# If there is some text, return it.
|
||||
# Eventually store the result so we can process it later.
|
||||
|
||||
if result.start() > 0:
|
||||
ret = self.buf[:result.start()]
|
||||
self.buf = self.buf[result.end():]
|
||||
|
@ -178,6 +190,7 @@ class TextoutStream:
|
|||
return ret
|
||||
|
||||
# Process the result now!
|
||||
|
||||
self.buf = self.buf[result.end():]
|
||||
data = TextoutUnit(result)
|
||||
self.last = data
|
|
@ -9,12 +9,11 @@
|
|||
|
||||
import string as _string
|
||||
from copy import deepcopy as _deepcopy
|
||||
from html import escape as _htmlescape
|
||||
from .tags import TextoutBlockTag as _TextoutBlockTag, \
|
||||
TextoutParagraphTag as _TextoutParagraphTag, get_tag as _get_tag
|
||||
from .stream import TextoutStream as _TextoutStream
|
||||
from .smileys import htmlsmileys as _htmlsmileys
|
||||
from .urls import htmlurls as _htmlurls
|
||||
|
||||
from ._options import TextoutBlockTag as _TextoutBlockTag, \
|
||||
TextoutParagraphTag as _TextoutParagraphTag, TextoutOptions as _Options
|
||||
from ._stream import TextoutStream as _TextoutStream
|
||||
from ._html import escape as _htmlescape, urls as _htmlurls
|
||||
|
||||
__all__ = ["Translator"]
|
||||
|
||||
|
@ -27,17 +26,26 @@ class _TweaksDictionary:
|
|||
tweak keyword, e.g. `label_prefix`, `LABELPREFIX` and
|
||||
`__LaBeL___PRE_FIX__`. """
|
||||
|
||||
def __normalize(self, name):
|
||||
return ''.join(c for c in name if c in _string.ascii_letters).lower()
|
||||
|
||||
def __init__(self, base):
|
||||
self.__elts = {}
|
||||
|
||||
for kw in base:
|
||||
self.__elts[self.__normalize(kw)] = base[kw]
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.__class__.__name__}({repr(self.__elts)})"
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__elts[key]
|
||||
return self.__elts[self.__normalize(key)]
|
||||
|
||||
def __getattr__(self, key):
|
||||
try:
|
||||
return self.__elts[self.__normalize(key)]
|
||||
except:
|
||||
raise AttributeError(key)
|
||||
|
||||
def __normalize(self, name):
|
||||
return ''.join(c for c in name if c in _string.ascii_letters).lower()
|
||||
|
||||
# ---
|
||||
# Tag data utility.
|
||||
|
@ -60,15 +68,32 @@ class _TagData:
|
|||
else self.INLINE
|
||||
self.full = full
|
||||
|
||||
# Tag stack behaviour.
|
||||
# `nwi` is whether the tag can be in itself directly or not (see
|
||||
# the `not_within_itself` property in the docs).
|
||||
# `onlyin` is the list of tags that are allowed as a parent tag
|
||||
# to this one.
|
||||
# `allowed` is the tags that are allowed amongst the children tags.
|
||||
|
||||
self.nwi = bool(tag.not_within_itself) \
|
||||
if hasattr(tag, 'not_within_itself') else False
|
||||
self.onlyin = list(tag.only_in) \
|
||||
if hasattr(tag, 'only_in') else None
|
||||
self.allowed = list(tag.allowed_tags) \
|
||||
if hasattr(tag, 'allowed_tags') else None
|
||||
|
||||
# Tag beginning displaying.
|
||||
# `notempty` is the moment when (and if) to start displaying the
|
||||
# tag's code and content.
|
||||
# `started` is whether the tag's beginning has been processed,
|
||||
# i.e. if the content is no longer processed.
|
||||
# `notext` is whether text within the tag directly is printed or not.
|
||||
|
||||
self.notempty = bool(tag.notempty) if hasattr(tag, 'notempty') \
|
||||
else False
|
||||
self.started = False
|
||||
self.notext = bool(tag.no_text) if hasattr(tag, 'no_text') \
|
||||
else False
|
||||
|
||||
# `base` is the actual tag object returned by `get_tag()`.
|
||||
|
||||
|
@ -77,11 +102,12 @@ class _TagData:
|
|||
# Flags and properties calculated from the tag's attributes, using the
|
||||
# rules given in `TAGS.md`.
|
||||
# `ign` is whether the content should be read while the tag is opened.
|
||||
# `raw` is whether the tag's content should be read as raw.
|
||||
# `generic` is whether the tag can be terminated by the generic
|
||||
# tag ending mark [/].
|
||||
# `notempty` is whether the tag should be used with an empty
|
||||
# content or not (e.g. to avoid `<p></p>`), True if not.
|
||||
# `raw` is whether the tag's content should be read as raw.
|
||||
# `super` is whether the tag is a superblock or not.
|
||||
# `inlined` is whether the next block on the same level is turned into
|
||||
# a superblock or not.
|
||||
|
||||
self.ign = not hasattr(tag, 'preprocess') and hasattr(tag, 'content')
|
||||
|
||||
|
@ -95,6 +121,12 @@ class _TagData:
|
|||
bool(tag.superblock) if hasattr(tag, 'superblock') \
|
||||
else False
|
||||
|
||||
self.inlined = bool(tag.inlined) if self.super \
|
||||
and hasattr(tag, 'inlined') and bool(tag.inlined) else False
|
||||
|
||||
self.noinline = bool(tag.noinline) if self.type == self.BLOCK \
|
||||
and hasattr(tag, 'noinline') else False
|
||||
|
||||
# Content processing utilities.
|
||||
# `last` is the content of the tag. A boolean indicates that we
|
||||
# only want to know if the content is empty or not, and a string
|
||||
|
@ -120,7 +152,7 @@ class _TagData:
|
|||
self.last = ""
|
||||
|
||||
def __repr__(self):
|
||||
return '<TagData>'
|
||||
return f'TagData(tag = {repr(self.tag)})'
|
||||
|
||||
# ---
|
||||
# Translator main class.
|
||||
|
@ -129,11 +161,11 @@ class _TagData:
|
|||
class Translator:
|
||||
""" One-time usage class for translating.
|
||||
Use it this way: `Translator(my_inp, my_outp).process()`.
|
||||
|
||||
|
||||
You can even chain calls as the `process()` method returns
|
||||
the output stream object. """
|
||||
|
||||
def __init__(self, inp, outp, output_type = 'html', tweaks = {}):
|
||||
def __init__(self, inp, outp, output_type, tweaks, options):
|
||||
""" Initializer. """
|
||||
|
||||
if not output_type in ('html', 'lightscript'):
|
||||
|
@ -141,6 +173,7 @@ class Translator:
|
|||
self.output_type = output_type
|
||||
|
||||
self.tweaks = _TweaksDictionary(tweaks)
|
||||
self.options = options
|
||||
|
||||
self.inp = inp
|
||||
self.outp = outp
|
||||
|
@ -174,24 +207,49 @@ class Translator:
|
|||
self.raw_mode = False
|
||||
self.raw_deg = 0
|
||||
|
||||
# `inline_mode` is whether the inline mode is on or not.
|
||||
# Actually, for now, this mode is only global and cannot be enabled
|
||||
# by tags.
|
||||
|
||||
self.inline_mode = bool(self.tweak("inline", False))
|
||||
|
||||
def __repr__(self):
|
||||
p = []
|
||||
p.append(f"inp = {repr(self.inp)}")
|
||||
p.append(f"outp = {repr(self.outp)}")
|
||||
p.append(f"output_type = {repr(self.output_type)}")
|
||||
p.append(f"tweaks = {repr(self.tweaks)}")
|
||||
p.append(f"options = {repr(self.options)}")
|
||||
|
||||
return f"{self.__class__.__name__}({', '.join(p)})"
|
||||
|
||||
def tweak(self, key, default = None):
|
||||
""" Get a tweak from the tweaks dictionary. """
|
||||
|
||||
try:
|
||||
return self.tweaks[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
# ---
|
||||
# Text outputting utilities.
|
||||
# ---
|
||||
|
||||
def process_text_group(self):
|
||||
def process_text(self, text):
|
||||
""" Process text groups for naked URLs and stuff. """
|
||||
|
||||
# In all cases, we want to escape for HTML things, so that the
|
||||
# user doesn't insert raw HTML tags (which would be a security flaw!).
|
||||
|
||||
if self.output_type == 'html':
|
||||
text = _htmlescape(self.text_group)
|
||||
text = _htmlescape(text)
|
||||
|
||||
# For non-raw HTML, we want to add smiley and URLs conversion,
|
||||
# because it's nicer!
|
||||
|
||||
if not self.raw_mode and self.output_type == 'html':
|
||||
text = _htmlsmileys(_htmlurls(text))
|
||||
text = _htmlurls(text)
|
||||
text = self.options.htmlsmileys(text)
|
||||
|
||||
return text
|
||||
|
||||
|
@ -199,9 +257,10 @@ class Translator:
|
|||
""" Output some text. """
|
||||
|
||||
# If we want to ignore the content (because it is not used
|
||||
# nor output), let the text fall into the void.
|
||||
# nor output or the current tag doesn't allow text), let the
|
||||
# text fall into the void.
|
||||
|
||||
if self.cign > 0:
|
||||
if self.cign > 0 or (self.queue and self.queue[0].notext):
|
||||
return
|
||||
|
||||
# Add to the text group, which will be processed when `flush_text()`
|
||||
|
@ -209,7 +268,8 @@ class Translator:
|
|||
|
||||
self.text_group += text
|
||||
|
||||
def flush_text(self):
|
||||
def flush_text(self, superblocks_only = False,
|
||||
next_block_is_super = False):
|
||||
""" Flush the text that has been output. """
|
||||
|
||||
# First of all, check if the text group is empty or if we want to
|
||||
|
@ -218,81 +278,126 @@ class Translator:
|
|||
if not self.text_group or self.cign > 0:
|
||||
return
|
||||
|
||||
# Pop the text group and put the code, with the process function in
|
||||
# case it is given to a non-raw processing tag or given to the
|
||||
# output.
|
||||
|
||||
text = self.text_group
|
||||
self.text_group = ""
|
||||
|
||||
self.add_text(text, process_func = lambda x: self.process_text(x),
|
||||
superblocks_only = superblocks_only,
|
||||
next_block_is_super = next_block_is_super)
|
||||
|
||||
# ---
|
||||
# Code outputting utilities.
|
||||
# ---
|
||||
|
||||
def add_text(self, text, process_func = lambda x: x, start_tags = True,
|
||||
superblocks_only = False, next_block_is_super = False,
|
||||
skip_first = False):
|
||||
""" Add text to the higher blocks if available. """
|
||||
|
||||
# The last queue is composed of booleans (does the group contain
|
||||
# something or not) and texts for content processing.
|
||||
# We want to set all of the booleans to True until the first text
|
||||
# group, to which we want to add the current text.
|
||||
# If there is no content preprocessing and we have to output it,
|
||||
# we want to start the tags first: `dat == None` will be our signal!
|
||||
#
|
||||
# Think about resetting `text_group` as its content has been used
|
||||
# somewhere (unbuffer data).
|
||||
|
||||
blockfound = False
|
||||
for dat in self.queue:
|
||||
# Check if it is a tag we want to contribute to.
|
||||
|
||||
if dat.type == dat.BLOCK:
|
||||
if dat.super or next_block_is_super:
|
||||
blockfound = True
|
||||
next_block_is_super = dat.inlined
|
||||
elif not superblocks_only and not blockfound:
|
||||
blockfound = True
|
||||
next_block_is_super = dat.inlined
|
||||
else:
|
||||
continue
|
||||
|
||||
# Check if it is the first tag we want to skip.
|
||||
|
||||
if skip_first:
|
||||
skip_first = False
|
||||
continue
|
||||
|
||||
# Contribute to it, either by or-ing the content if it is
|
||||
# a boolean (but anything or True == True), or by contributing
|
||||
# to the buffer otherwise.
|
||||
|
||||
if isinstance(dat.last, bool):
|
||||
dat.last = True
|
||||
continue
|
||||
dat.last += self.text_group
|
||||
|
||||
# Start the tags if we're about to give this content to
|
||||
# preprocessing.
|
||||
|
||||
if start_tags:
|
||||
self.start_tags()
|
||||
|
||||
# Add the content to the preprocess buffer.
|
||||
|
||||
if not dat.raw:
|
||||
text = process_func(text)
|
||||
dat.last += text
|
||||
|
||||
break
|
||||
else:
|
||||
dat = None
|
||||
text = self.process_text_group()
|
||||
# No `break` has been encountered, which means the content has
|
||||
# not been added to any preprocessing tag. Please process it!
|
||||
|
||||
self.text_group = ""
|
||||
if start_tags:
|
||||
self.start_tags()
|
||||
self.outp.write(process_func(text))
|
||||
|
||||
# Start the tags that haven't been started, and stuff.
|
||||
return False
|
||||
|
||||
self.start_tags()
|
||||
# The content has been given for preprocessing.
|
||||
|
||||
# If the content has to be written, we ought to.
|
||||
return True
|
||||
|
||||
if dat == None:
|
||||
self.outp.write(text)
|
||||
def put_debug(self, message):
|
||||
""" Put a debug message directly into the output. """
|
||||
|
||||
# ---
|
||||
# Code outputting utilities.
|
||||
# ---
|
||||
self.outp.write(message)
|
||||
|
||||
def put_code(self, code):
|
||||
def put_code(self, code, start_tags = True, flush_text = True,
|
||||
superblocks_only = True, next_block_is_super = False,
|
||||
skip_first = False):
|
||||
""" Put some code. """
|
||||
|
||||
# We don't want to mix text and code, so we'll flush to be sure that
|
||||
# the order doesn't get mixed up.
|
||||
|
||||
self.flush_text()
|
||||
if flush_text:
|
||||
self.flush_text()
|
||||
|
||||
# First of all, check if the text is empty or if we want to ignore it.
|
||||
|
||||
if not code or self.cign > 0:
|
||||
return
|
||||
|
||||
# As in `flush_text()`, the last queue is composed of booleans.
|
||||
# We want to set all of the booleans to True until the first text
|
||||
# group, to which we want to add the current text.
|
||||
# If there is no content preprocessing and we have to output it,
|
||||
# we want to start the tags first: `dat == None` will be our signal!
|
||||
# Add the code.
|
||||
|
||||
for dat in self.queue:
|
||||
if isinstance(dat.last, bool):
|
||||
dat.last = True
|
||||
continue
|
||||
dat.last += code
|
||||
break
|
||||
else:
|
||||
dat = None
|
||||
|
||||
# Start the tags that haven't been started, and stuff.
|
||||
|
||||
self.start_tags()
|
||||
|
||||
# If the content has to be written, we ought to.
|
||||
|
||||
if dat == None:
|
||||
self.outp.write(code)
|
||||
self.add_text(code, start_tags = start_tags,
|
||||
superblocks_only = superblocks_only,
|
||||
next_block_is_super = next_block_is_super,
|
||||
skip_first = skip_first)
|
||||
|
||||
def put_newline(self):
|
||||
""" Put a newline. """
|
||||
|
||||
# If we want to ignore the content (because it is not used
|
||||
# nor output or the current tag doesn't allow text), let the
|
||||
# text fall into the void.
|
||||
|
||||
if self.cign > 0 or (self.queue and self.queue[0].notext):
|
||||
return
|
||||
|
||||
# The newline depends on the output type and the context, of course.
|
||||
|
||||
if self.output_type == 'html' and not self.raw_mode:
|
||||
|
@ -317,10 +422,10 @@ class Translator:
|
|||
if dat.ign:
|
||||
self.cign += 1
|
||||
|
||||
# If it is a block, end the current block.
|
||||
# If we're about to put a tag or anything, empty the text block
|
||||
# here.
|
||||
|
||||
if dat.type == dat.BLOCK:
|
||||
self.end_block()
|
||||
self.flush_text()
|
||||
|
||||
# Insert the tag into the queue.
|
||||
|
||||
|
@ -342,6 +447,9 @@ class Translator:
|
|||
`end` represents the full version of the ending tag marker,
|
||||
for displaying if the tag is invalid. """
|
||||
|
||||
if not self.queue:
|
||||
return
|
||||
|
||||
# Even if we had no beginning, no content and no end, what is
|
||||
# here has to be distinguished from what was right before!
|
||||
# So we need to flush the text group for this.
|
||||
|
@ -356,6 +464,9 @@ class Translator:
|
|||
dat = self.queue.pop(0)
|
||||
tag = dat.tag
|
||||
|
||||
pcattrs = {'superblocks_only': dat.type == dat.BLOCK,
|
||||
'next_block_is_super': dat.inlined}
|
||||
|
||||
# If preprocessing has been enabled, we ought to process the content,
|
||||
# check if the tag is valid, and do everything we would have done
|
||||
# while pushing the tag if it didn't do content processing.
|
||||
|
@ -401,26 +512,27 @@ class Translator:
|
|||
# Output the beginning and the content. If there was no content,
|
||||
# just put the content that we got earlier.
|
||||
|
||||
dat.started = True
|
||||
if hasattr(tag, 'begin'):
|
||||
self.put_code(tag.begin())
|
||||
self.put_code(tag.begin(), **pcattrs)
|
||||
dat.started = True
|
||||
|
||||
if hasattr(tag, 'content'):
|
||||
self.put_code(tag.content())
|
||||
self.put_code(tag.content(), **pcattrs)
|
||||
elif dat.raw:
|
||||
# XXX: I'm unsure about this. Shall raw tags return code
|
||||
# or text? The text will only be escaped as raw mode is
|
||||
# still enabled at this point.
|
||||
|
||||
self.put_text(content)
|
||||
else:
|
||||
self.put_code(content)
|
||||
self.put_code(content, **pcattrs)
|
||||
elif hasattr(tag, 'content'):
|
||||
# Tag replaces content without preprocessing, which means
|
||||
# the content has been ignored and the tag only puts the
|
||||
# things.
|
||||
|
||||
self.cign -= 1
|
||||
self.put_code(tag.content())
|
||||
self.put_code(tag.content(), **pcattrs)
|
||||
elif hasattr(tag, 'default'):
|
||||
# Tag defines a default content if there might be none,
|
||||
# without text preprocessing. If there is no content, print it.
|
||||
|
@ -428,6 +540,11 @@ class Translator:
|
|||
# an exception if the tag in its current configuration should
|
||||
# not have an empty content.
|
||||
|
||||
if not dat.started:
|
||||
if hasattr(dat.tag, 'begin'):
|
||||
self.put_code(dat.tag.begin(), **pcattrs)
|
||||
dat.started = True
|
||||
|
||||
if not dat.last:
|
||||
try:
|
||||
self.put_text(tag.default())
|
||||
|
@ -442,15 +559,13 @@ class Translator:
|
|||
|
||||
# Don't forget to end the tag!
|
||||
|
||||
if dat.notempty and not dat.started:
|
||||
if not dat.started:
|
||||
pass
|
||||
elif dat.type != dat.BLOCK or dat.super:
|
||||
if hasattr(tag, 'end'):
|
||||
self.put_code(tag.end())
|
||||
else:
|
||||
self.queue.insert(0, dat)
|
||||
self.end_block()
|
||||
self.queue.pop(0)
|
||||
if dat.type == dat.BLOCK:
|
||||
self.close_inline_tags()
|
||||
if hasattr(tag, 'end'):
|
||||
self.put_code(tag.end(), start_tags = False, **pcattrs)
|
||||
|
||||
# Disable raw mode if it was a raw tag (which means that it enabled it,
|
||||
# as tags into raw tags cannot be processed).
|
||||
|
@ -464,85 +579,99 @@ class Translator:
|
|||
|
||||
def start_tags(self):
|
||||
""" Start the tags that haven't been started yet.
|
||||
If a block has been newly opened, we ought to close the block at
|
||||
the same level as them before opening it.
|
||||
This is usually called when content is output, for tags that
|
||||
aren't empty. """
|
||||
|
||||
# First, get the references to the block and inline tags that need
|
||||
# to be started.
|
||||
# First, get the references to the blocks to end, the blocks to
|
||||
# start, and all of the inline tags.
|
||||
|
||||
blocks = []
|
||||
superblocks = []
|
||||
block_to_start = None
|
||||
block_to_end = None
|
||||
inlines = []
|
||||
for dat in self.queue:
|
||||
# Check if the tag hasn't already been started or doesn't call
|
||||
|
||||
next_block_is_super = False
|
||||
for idx, dat in enumerate(self.queue):
|
||||
# Check that the tag hasn't already been started or doesn't call
|
||||
# for content processing.
|
||||
|
||||
if type(dat.last) != bool: break
|
||||
if dat.notempty and not dat.last: break
|
||||
if dat.started: continue
|
||||
|
||||
# Then put the tag in the appropriate queue, and set it as
|
||||
# started for methods as `put_code()` that call this method
|
||||
# back not to re-put anything.
|
||||
|
||||
if dat.type == dat.BLOCK:
|
||||
blocks.insert(0, dat)
|
||||
else:
|
||||
inlines.insert(0, dat)
|
||||
|
||||
# Only select super blocks and the last non-super block.
|
||||
|
||||
selected = [block for block in blocks if block.super]
|
||||
if blocks and not blocks[0].super:
|
||||
selected.insert(0, blocks[0])
|
||||
blocks = selected
|
||||
|
||||
# Then, put the tag beginnings.
|
||||
|
||||
for dat in blocks + inlines:
|
||||
dat.started = True
|
||||
if hasattr(dat.tag, 'begin'):
|
||||
self.put_code(dat.tag.begin())
|
||||
|
||||
def end_block(self):
|
||||
""" End the current block. """
|
||||
|
||||
queue = self.queue.copy()
|
||||
|
||||
# We want to collect inline and block tags, in the order they
|
||||
# were inserted, reversed.
|
||||
|
||||
blocks = []
|
||||
inlines = []
|
||||
for dat in self.queue:
|
||||
# Check if the tag has been started and if it is a super
|
||||
# block (which means we want to stop here).
|
||||
|
||||
if dat.super: break
|
||||
if not dat.started: continue
|
||||
if idx > 0 and type(dat.last) != bool:
|
||||
break
|
||||
|
||||
# Then put the tag in the appropriate queue.
|
||||
|
||||
if dat.type == dat.BLOCK:
|
||||
blocks.append(dat)
|
||||
if block_to_start is not None and \
|
||||
dat.super or next_block_is_super:
|
||||
# The block is to be considered as the block to start.
|
||||
# Sometimes the block to start is the latest superblock!
|
||||
|
||||
superblocks.insert(0, dat)
|
||||
next_block_is_super = dat.inlined
|
||||
elif dat.started:
|
||||
block_to_end = dat
|
||||
next_block_is_super = dat.inlined
|
||||
elif block_to_end is None and block_to_start is None:
|
||||
block_to_start = dat
|
||||
next_block_is_super = dat.inlined
|
||||
else:
|
||||
inlines.append(dat)
|
||||
inlines.insert(0, dat)
|
||||
|
||||
# Then we want to end the tags, and reset them in case we're going
|
||||
# to use them.
|
||||
# If there is no new block to start, there's no need to end the
|
||||
# current block.
|
||||
|
||||
self.queue = inlines + blocks
|
||||
while self.queue:
|
||||
dat = self.queue.pop(0)
|
||||
tag = dat.tag
|
||||
if hasattr(tag, 'end'):
|
||||
self.put_code(tag.end())
|
||||
if not block_to_start:
|
||||
block_to_end = None
|
||||
|
||||
# Put the tag ends for the blocks to end.
|
||||
# If there are some, we ought to close the inline tags first.
|
||||
|
||||
if block_to_end is not None:
|
||||
for dat in inlines[::-1] + [block_to_end]:
|
||||
if not dat.started:
|
||||
continue
|
||||
|
||||
if hasattr(dat.tag, 'end'):
|
||||
self.put_code(dat.tag.end(), start_tags = False,
|
||||
skip_first = True)
|
||||
dat.started = False
|
||||
dat.reset()
|
||||
|
||||
# Then, put the tag beginnings.
|
||||
|
||||
to_begin = superblocks \
|
||||
+ ([block_to_start] if block_to_start else [])
|
||||
if all(not x.noinline for x in to_begin):
|
||||
to_begin += inlines
|
||||
|
||||
for dat in to_begin:
|
||||
if dat.started:
|
||||
continue
|
||||
if dat.notempty and not dat.last:
|
||||
break
|
||||
|
||||
if hasattr(dat.tag, 'begin'):
|
||||
self.put_code(dat.tag.begin(), start_tags = False,
|
||||
flush_text = False, skip_first = dat == self.queue[0])
|
||||
dat.started = True
|
||||
|
||||
def close_inline_tags(self):
|
||||
""" We're about to close a block, so we want to close any inline tags
|
||||
that could have been taken within it. """
|
||||
|
||||
for dat in self.queue:
|
||||
# Check that the tag hasn't already been closed.
|
||||
|
||||
if dat.type != dat.INLINE or not dat.started:
|
||||
continue
|
||||
|
||||
if hasattr(dat.tag, 'end'):
|
||||
self.put_code(dat.tag.end(), start_tags = False)
|
||||
dat.started = False
|
||||
dat.reset()
|
||||
|
||||
# Restore the queue.
|
||||
|
||||
self.queue = queue
|
||||
|
||||
# ---
|
||||
# Main function.
|
||||
# ---
|
||||
|
@ -553,8 +682,9 @@ class Translator:
|
|||
# By default, everything is in a paragraph.
|
||||
# Other blocks will supplant this by being further in the queue.
|
||||
|
||||
self.push_tag(_TagData(_TextoutParagraphTag(None, None,
|
||||
self.output_type, self.tweaks), None, ''))
|
||||
if not self.inline_mode:
|
||||
self.push_tag(_TagData(_TextoutParagraphTag(None, None,
|
||||
self.output_type, self.tweaks, self.options), None, ''))
|
||||
|
||||
# We want to get our elements out of the element stream (Lephe
|
||||
# told me that the `TextoutStream` class was actually a lexer,
|
||||
|
@ -655,22 +785,79 @@ class Translator:
|
|||
# Get the initialized tag with the name and value.
|
||||
# If the tag is unknown, output the full thing and just go on.
|
||||
|
||||
tag = _get_tag(tagdata.name, tagdata.value, self.output_type,
|
||||
self.tweaks)
|
||||
if not tag:
|
||||
try:
|
||||
tag = self.options.get_tag(tagdata.name)
|
||||
except:
|
||||
self.put_text(tagdata.full)
|
||||
continue
|
||||
|
||||
# And don't forget to push the tag.
|
||||
value = tagdata.value
|
||||
if value != None and hasattr(tag, 'procvalue') and tag.procvalue:
|
||||
value = self.process_text(value)
|
||||
|
||||
try:
|
||||
tag = tag(tagdata.name, value, self.output_type, self.tweaks,
|
||||
self.options)
|
||||
except:
|
||||
self.put_text(tagdata.full)
|
||||
continue
|
||||
|
||||
# Check if it is a block tag.
|
||||
|
||||
dat = _TagData(tag, tagdata.name, tagdata.full)
|
||||
if self.inline_mode and dat.type == dat.BLOCK:
|
||||
self.put_text(tagdata.full)
|
||||
continue
|
||||
|
||||
# Check if is an allowed tag.
|
||||
|
||||
if dat.type == dat.BLOCK:
|
||||
try:
|
||||
sb = next(d for d in self.queue if d.super)
|
||||
except StopIteration:
|
||||
alw = None
|
||||
else:
|
||||
alw = sb.allowed
|
||||
else:
|
||||
try:
|
||||
pr = self.queue[0]
|
||||
except IndexError:
|
||||
alw = None
|
||||
else:
|
||||
alw = pr.allowed
|
||||
|
||||
if alw is not None and not any(cls for cls in alw \
|
||||
if isinstance(dat.base, cls)):
|
||||
self.put_text(tagdata.full)
|
||||
continue
|
||||
|
||||
# Check if it is within itself and it can't.
|
||||
|
||||
if dat.nwi and any(d for d in self.queue \
|
||||
if isinstance(d.base, type(dat.base))):
|
||||
while not isinstance(self.queue[0].base, type(dat.base)):
|
||||
self.pop_tag()
|
||||
self.pop_tag()
|
||||
|
||||
# Check if it is allowed in this parent.
|
||||
|
||||
if dat.onlyin is not None and self.queue \
|
||||
and not any(cls for cls in dat.onlyin \
|
||||
if isinstance(self.queue[0].base, cls)):
|
||||
self.put_text(tagdata.full)
|
||||
continue
|
||||
|
||||
# And don't forget to push the tag (through its data).
|
||||
|
||||
self.push_tag(dat)
|
||||
|
||||
# Push a paragraph tag if the block is a superblock.
|
||||
|
||||
if dat.type == dat.BLOCK and dat.super:
|
||||
if dat.type == dat.BLOCK and dat.super and not dat.raw \
|
||||
and not dat.inlined and (dat.allowed is None \
|
||||
or _TextoutParagraphTag in dat.allowed):
|
||||
self.push_tag(_TagData(_TextoutParagraphTag(None, None,
|
||||
self.output_type, self.tweaks), None, ''))
|
||||
self.output_type, self.tweaks, self.options), None, ''))
|
||||
|
||||
# End of file, it seems! Let's close the tags, flush the text
|
||||
# and just resume our lives from there.
|
||||
|
@ -684,4 +871,10 @@ class Translator:
|
|||
|
||||
return self.outp
|
||||
|
||||
def reopen(self, inp, outp):
|
||||
""" Open another instance of this translator for sub-translators. """
|
||||
|
||||
return Translator(inp, outp, self.output_type, self.tweaks,
|
||||
self.options)
|
||||
|
||||
# End of file.
|
|
@ -4,32 +4,45 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutBlockTag as _TextoutBlockTag
|
||||
from .. import BlockTag as _BlockTag
|
||||
|
||||
__all__ = ["TextoutAlignTag"]
|
||||
__all__ = ["AlignTag"]
|
||||
|
||||
class TextoutAlignTag(_TextoutBlockTag):
|
||||
|
||||
class AlignTag(_BlockTag):
|
||||
""" Main tag for aligning paragraphs.
|
||||
Example uses:
|
||||
|
||||
|
||||
[align=center]This text is centered horizontally.[/align]
|
||||
[justify]This text is justified.[/justify]
|
||||
"""
|
||||
|
||||
aliases = ('[align]', '[center]', '[left]', '[right]', '[justify]')
|
||||
aliases = ('[align]', '[center]', '[centre]', '[left]', '[right]',
|
||||
'[justify]')
|
||||
superblock = True
|
||||
notempty = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
align = None
|
||||
if not name: pass
|
||||
elif name == 'align' and value != None:
|
||||
align = value
|
||||
elif name[1:-1] in ('center', 'left', 'right', 'justify'):
|
||||
align = name[1:-1]
|
||||
_align = {
|
||||
'center': 'center',
|
||||
'centre': 'center',
|
||||
'left': 'left',
|
||||
'right': 'right',
|
||||
'justify': 'justify'}
|
||||
|
||||
if not name:
|
||||
align = None
|
||||
elif name == 'align' and value is not None:
|
||||
align = _align[value]
|
||||
else:
|
||||
align = _align[name[1:-1]]
|
||||
|
||||
self._align = align
|
||||
|
||||
def begin_html(self):
|
||||
if not self._align:
|
||||
return ''
|
||||
|
||||
cl = []
|
||||
if self._align:
|
||||
cl.append('align-' + self._align)
|
||||
|
@ -37,6 +50,8 @@ class TextoutAlignTag(_TextoutBlockTag):
|
|||
return '<div{}>'.format(' class="' + ' '.join(cl) + '"' if cl else '')
|
||||
|
||||
def end_html(self):
|
||||
if not self._align:
|
||||
return ''
|
||||
return '</div>'
|
||||
|
||||
# End of file.
|
|
@ -4,15 +4,15 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutBlockTag as _TextoutBlockTag, \
|
||||
TextoutInlineTag as _TextoutInlineTag
|
||||
from .. import BlockTag as _BlockTag, InlineTag as _InlineTag
|
||||
|
||||
__all__ = ["TextoutCodeTag", "TextoutInlineCodeTag", "TextoutNoEvalTag"]
|
||||
__all__ = ["CodeTag", "InlineCodeTag", "NoEvalTag"]
|
||||
|
||||
class TextoutCodeTag(_TextoutBlockTag):
|
||||
|
||||
class CodeTag(_BlockTag):
|
||||
""" The basic code tag, for displaying code.
|
||||
Example uses:
|
||||
|
||||
|
||||
[code]int main()
|
||||
{
|
||||
printf("hello, world");
|
||||
|
@ -21,6 +21,7 @@ class TextoutCodeTag(_TextoutBlockTag):
|
|||
aliases = ('[code]',)
|
||||
generic = False
|
||||
raw = True
|
||||
notempty = True
|
||||
|
||||
def begin_html(self):
|
||||
return '<div class="code">'
|
||||
|
@ -34,11 +35,12 @@ class TextoutCodeTag(_TextoutBlockTag):
|
|||
def end_lightscript(self):
|
||||
return '```\n'
|
||||
|
||||
class TextoutInlineCodeTag(_TextoutInlineTag):
|
||||
|
||||
class InlineCodeTag(_InlineTag):
|
||||
""" Inline code tag, doesn't display a box, simply doesn't evaluate
|
||||
the content and uses monospace font.
|
||||
Example uses:
|
||||
|
||||
|
||||
`some inline code`
|
||||
[inlinecode][b]The tags will be shown verbatim.[/b][/inlinecode]
|
||||
[inlinecode][inlinecode][i]This also[/inlinecode] works![/inlinecode]
|
||||
|
@ -49,7 +51,7 @@ class TextoutInlineCodeTag(_TextoutInlineTag):
|
|||
raw = True
|
||||
|
||||
def begin_html(self):
|
||||
return '<span style="font-family: monospace;">'
|
||||
return '<span class="inline-code">'
|
||||
|
||||
def end_html(self):
|
||||
return '</span>'
|
||||
|
@ -60,14 +62,15 @@ class TextoutInlineCodeTag(_TextoutInlineTag):
|
|||
def end_lightscript(self):
|
||||
return '`'
|
||||
|
||||
class TextoutNoEvalTag(_TextoutInlineTag):
|
||||
|
||||
class NoEvalTag(_InlineTag):
|
||||
""" Inline code tag, simply doesn't evaluate the content.
|
||||
Example uses:
|
||||
|
||||
|
||||
[noeval][b]wow, and no need for monospace![/b][/noeval]
|
||||
"""
|
||||
|
||||
aliases = ('[noeval]',)
|
||||
aliases = ('[noeval]', '[nobbcode]')
|
||||
generic = False
|
||||
raw = True
|
||||
|
|
@ -4,15 +4,18 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutBlockTag as _TextoutBlockTag
|
||||
import urllib.parse as _urlparse
|
||||
|
||||
from .. import BlockTag as _BlockTag
|
||||
from html import escape as _htmlescape
|
||||
|
||||
__all__ = ["TextoutImageTag", "TextoutAdminImageTag"]
|
||||
__all__ = ["ImageTag", "AdminImageTag"]
|
||||
|
||||
class TextoutImageTag(_TextoutBlockTag):
|
||||
|
||||
class ImageTag(_BlockTag):
|
||||
""" The main tag for displaying an image.
|
||||
Example uses:
|
||||
|
||||
|
||||
[img]picture_url[/img]
|
||||
[img=center]picture_url[/img]
|
||||
[img=12x24]picture_url[/img]
|
||||
|
@ -24,12 +27,25 @@ class TextoutImageTag(_TextoutBlockTag):
|
|||
raw = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
_align = {
|
||||
'center': ('center', False),
|
||||
'centre': ('center', False),
|
||||
'left': ('left', False),
|
||||
'right': ('right', False),
|
||||
'float': (None, True),
|
||||
'floating': (None, True),
|
||||
'float-left': ('left', True),
|
||||
'float-center': ('center', True),
|
||||
'float-centre': ('center', True),
|
||||
'float-right': ('right', True),
|
||||
}
|
||||
|
||||
self._width = None
|
||||
self._height = None
|
||||
self._align = None
|
||||
self._float = False
|
||||
|
||||
for arg in ("", value)[value != None].split('|'):
|
||||
for arg in ("", value)[value is not None].split('|'):
|
||||
if not arg:
|
||||
pass
|
||||
elif arg[0] in '0123456789x':
|
||||
|
@ -37,28 +53,36 @@ class TextoutImageTag(_TextoutBlockTag):
|
|||
self._height = None
|
||||
|
||||
dim = arg.split('x')
|
||||
try: self._width = int(dim[0])
|
||||
except: pass
|
||||
try: self._height = int(dim[1])
|
||||
except: pass
|
||||
elif arg in ('center', 'left', 'right'):
|
||||
self._align = arg
|
||||
elif arg in ('float-left', 'float-right'):
|
||||
self._align = arg[6:]
|
||||
self._float = True
|
||||
elif arg in ('float', 'floating'):
|
||||
self._float = True
|
||||
try:
|
||||
self._width = int(dim[0])
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
self._height = int(dim[1])
|
||||
except ValueError:
|
||||
pass
|
||||
elif arg in _align:
|
||||
al, fl = _align[arg]
|
||||
if al is not None:
|
||||
self._align = al
|
||||
if fl:
|
||||
self._float = True
|
||||
|
||||
def preprocess(self, content):
|
||||
for prefix in ('http://', 'https://', 'ftp://', '/'):
|
||||
if content.startswith(prefix):
|
||||
break
|
||||
else:
|
||||
raise Exception("No allowed prefix!")
|
||||
try:
|
||||
self._image = self.image(content)
|
||||
except:
|
||||
url = _urlparse.urlparse(content)
|
||||
if url.scheme not in ('http', 'https'):
|
||||
raise Exception("No allowed prefix!")
|
||||
|
||||
self._url = content
|
||||
self._image = content
|
||||
|
||||
def content_html(self):
|
||||
if isinstance(self._image, str):
|
||||
url = _htmlescape(self._image)
|
||||
return '<p><a href="{}">{}</a></p>'.format(url, url)
|
||||
|
||||
style = []
|
||||
cls = []
|
||||
if self._width:
|
||||
|
@ -70,25 +94,25 @@ class TextoutImageTag(_TextoutBlockTag):
|
|||
elif self._width:
|
||||
style.append('height: auto')
|
||||
if self._float:
|
||||
cls.append('img-float')
|
||||
if self._align:
|
||||
cls.append('img-float-{}'.format(self._align or 'right'))
|
||||
elif self._align:
|
||||
cls.append('img-{}'.format(self._align))
|
||||
|
||||
return '<img src="{}"{}{} />'.format(\
|
||||
_htmlescape(self._url),
|
||||
return '<img src="{}"{}{} />'.format(_htmlescape(self._image.embed),
|
||||
' class="{}"'.format(' '.join(cls)) if cls else '',
|
||||
' style="{}"'.format('; '.join(style)) if style else '')
|
||||
|
||||
def content_lightscript(self):
|
||||
url = self._url.replace('[', '%5B').replace(']', '%5D')
|
||||
url = self._image.embed.replace('[', '%5B').replace(']', '%5D')
|
||||
return '[[image:{}]]'.format(url)
|
||||
|
||||
class TextoutAdminImageTag(TextoutImageTag):
|
||||
|
||||
class AdminImageTag(ImageTag):
|
||||
""" This tag is special for Planète Casio, as it takes images from
|
||||
the `ad`ministration's image folder.
|
||||
It just adds this folder's prefix.
|
||||
Example uses:
|
||||
|
||||
|
||||
[adimg]some_picture.png[/img]
|
||||
[adimg=center]some_picture.png[/img]
|
||||
[adimg=12x24]some_picture.png[/img]
|
||||
|
@ -99,8 +123,7 @@ class TextoutAdminImageTag(TextoutImageTag):
|
|||
aliases = ('[adimg]',)
|
||||
|
||||
def preprocess(self, content):
|
||||
path = content
|
||||
# FIXME: check image URL!
|
||||
self._url = 'https://www.planet-casio.com/images/ad/' + path
|
||||
self._url = 'https://www.planet-casio.com/images/ad/' + content
|
||||
self._checkurl()
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
import urllib.parse as _urlparse
|
||||
|
||||
from .. import Image as _Image
|
||||
|
||||
__all__ = ["GenericImage"]
|
||||
|
||||
|
||||
class GenericImage(_Image):
|
||||
""" Get a direct image. Actually this doesn't test anything, we should
|
||||
use like the Embed module again, as for videos. """
|
||||
|
||||
# FIXME: make that disappear one day for the OpenWebImage.
|
||||
|
||||
def __init__(self, content):
|
||||
url = _urlparse.urlparse(content)
|
||||
if url.scheme not in ('http', 'https'):
|
||||
raise Exception("No allowed prefix!")
|
||||
|
||||
self.embed = content
|
||||
|
||||
# WARNING: This is only for demonstration sake. Do not use without a cache!
|
||||
# This demonstration class uses the `embed-python` module.
|
||||
#
|
||||
#from embed import Embed as _Embed
|
||||
#
|
||||
#class OpenWebImage(_Image):
|
||||
# """ Decentralized way to gather an image data. """
|
||||
#
|
||||
# def __init__(self, url):
|
||||
# u = _urlparse.urlparse(url)
|
||||
# if not u.scheme in ('https',):
|
||||
# raise Exception
|
||||
#
|
||||
# embed = _Embed(url)
|
||||
# embed = embed.embed
|
||||
# assert embed['type'] == 'image'
|
||||
#
|
||||
# self.embed = embed['url']
|
||||
|
||||
# End of file.
|
|
@ -4,17 +4,18 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutInlineTag as _TextoutInlineTag
|
||||
from .. import InlineTag as _InlineTag
|
||||
import re as _re
|
||||
|
||||
__all__ = ["TextoutLabelTag", "TextoutTargetTag"]
|
||||
__all__ = ["LabelTag", "TargetTag"]
|
||||
|
||||
_labelexpr = _re.compile('^[a-z0-9-]{1,16}$', _re.I)
|
||||
|
||||
class TextoutLabelTag(_TextoutInlineTag):
|
||||
|
||||
class LabelTag(_InlineTag):
|
||||
""" The label tag, defines an anchor at a point of the post.
|
||||
Example uses:
|
||||
|
||||
|
||||
[label=installation]Installation de tel logiciel... (no ending req.)
|
||||
[label=compilation][/label] Compilation de tel logiciel...
|
||||
"""
|
||||
|
@ -33,10 +34,11 @@ class TextoutLabelTag(_TextoutInlineTag):
|
|||
name = self.tweak("label_prefix", "") + self._label
|
||||
return '<a name="{}"></a>'.format(name)
|
||||
|
||||
class TextoutTargetTag(_TextoutInlineTag):
|
||||
|
||||
class TargetTag(_InlineTag):
|
||||
""" The goto tag, links to an anchor defined in the post.
|
||||
Example uses:
|
||||
|
||||
|
||||
[target=installation]Check out the installation manual[/target]!
|
||||
"""
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from .. import InlineTag as _InlineTag
|
||||
from html import escape as _htmlescape
|
||||
|
||||
__all__ = ["LinkTag", "ProfileTag", "TopicTag", "TutorialTag", "ProgramTag"]
|
||||
|
||||
|
||||
class LinkTag(_InlineTag):
|
||||
""" The main link tag.
|
||||
Example uses:
|
||||
|
||||
[url=https://example.org/hi]Go to example.org[/url]!
|
||||
[url=/Fr/index.php][/url]
|
||||
[url]https://random.org/randomize.php[/url] """
|
||||
|
||||
aliases = ('[url]',)
|
||||
raw = True
|
||||
|
||||
def _validate(self):
|
||||
for prefix in ('http://', 'https://', 'ftp://', '/', '#'):
|
||||
if self._url.startswith(prefix):
|
||||
break
|
||||
else:
|
||||
raise Exception("No allowed prefix!")
|
||||
|
||||
def prepare(self, name, value):
|
||||
self._url = None
|
||||
|
||||
# If there is no value, wait until we have a content to
|
||||
# decide if we are valid or not.
|
||||
|
||||
if value is None:
|
||||
self.preprocess = self._preprocess_if_no_value
|
||||
return
|
||||
|
||||
# Otherwise, get the URL and validate.
|
||||
|
||||
self._url = value
|
||||
self._validate()
|
||||
self.default = self._default_if_value
|
||||
|
||||
def _default_if_value(self):
|
||||
return self._url
|
||||
|
||||
def _preprocess_if_no_value(self, content):
|
||||
self._url = content
|
||||
self._validate()
|
||||
|
||||
def begin_html(self):
|
||||
return '<a href="{}">'.format(_htmlescape(self._url))
|
||||
|
||||
def end_html(self):
|
||||
return '</a>'
|
||||
|
||||
def begin_lightscript(self):
|
||||
return '['
|
||||
|
||||
def end_lightscript(self):
|
||||
url = self._url.replace('(', '%28').replace(')', '%29')
|
||||
return ']({})'.format(url)
|
||||
|
||||
|
||||
class ProfileTag(LinkTag):
|
||||
""" A special link tag for Planète Casio's profiles.
|
||||
Adds the prefix to the content, and sets the value.
|
||||
Example uses:
|
||||
|
||||
[profil]Cakeisalie5[/] """
|
||||
|
||||
aliases = ('[profil]', '[profile]')
|
||||
|
||||
def prepare(self, name, value):
|
||||
# Override the LinkTag's prepare method.
|
||||
|
||||
pass
|
||||
|
||||
def preprocess(self, content):
|
||||
# Check the username's content (see `check(…, "pseudo")` in PCv42).
|
||||
|
||||
username = content
|
||||
allowed = "abcdefghijklmnopqrstuvwxyz0123456789_ -."
|
||||
if any(car not in allowed for car in allowed):
|
||||
raise ValueError("invalid username!")
|
||||
|
||||
# Prepare the tag.
|
||||
|
||||
self._url = 'https://www.planet-casio.com/Fr/compte/voir_profil.php' \
|
||||
'?membre={}'.format(username)
|
||||
self._validate()
|
||||
|
||||
|
||||
class TopicTag(LinkTag):
|
||||
""" A special link tag for Planète Casio's topics.
|
||||
Adds the prefix to the content, and sets the value.
|
||||
Example uses:
|
||||
|
||||
[topic]234[/] """
|
||||
|
||||
aliases = ('[topic]',)
|
||||
|
||||
def prepare(self, name, value):
|
||||
# Override the LinkTag's prepare method.
|
||||
|
||||
pass
|
||||
|
||||
def preprocess(self, content):
|
||||
# Check the topic number.
|
||||
|
||||
topic = int(content)
|
||||
|
||||
# Prepare the tag.
|
||||
|
||||
self._url = 'https://www.planet-casio.com/Fr/forums/' \
|
||||
f'lecture_sujet.php?id={topic}'
|
||||
self._validate()
|
||||
|
||||
|
||||
class TutorialTag(LinkTag):
|
||||
""" A special link tag for Planète Casio's tutorial.
|
||||
Adds the prefix to the content, and sets the value.
|
||||
Example uses:
|
||||
|
||||
[tutorial]71[/tutorial]
|
||||
[tuto]71[/tuto] """
|
||||
|
||||
aliases = ('[tutorial]', '[tuto]')
|
||||
|
||||
def prepare(self, name, value):
|
||||
# Override the LinkTag's prepare method.
|
||||
|
||||
pass
|
||||
|
||||
def preprocess(self, content):
|
||||
# Check the topic number.
|
||||
|
||||
topic = int(content)
|
||||
|
||||
# Prepare the tag.
|
||||
|
||||
self._url = 'https://www.planet-casio.com/Fr/programmation/' \
|
||||
f'tutoriels.php?id={topic}'
|
||||
self._validate()
|
||||
|
||||
|
||||
class ProgramTag(LinkTag):
|
||||
""" A special link tag for a Planète Casio's program.
|
||||
Adds the prefix to the content, and sets the value.
|
||||
Example uses:
|
||||
|
||||
[program]3598[/program]
|
||||
[prog]3598[/prog] """
|
||||
|
||||
aliases = ('[program]', '[prog]')
|
||||
|
||||
def prepare(self, name, value):
|
||||
# Override the LinkTag's prepare method.
|
||||
|
||||
pass
|
||||
|
||||
def preprocess(self, content):
|
||||
# Check the program number.
|
||||
|
||||
program = int(content)
|
||||
|
||||
# Prepare the tag.
|
||||
|
||||
self._url = 'https://www.planet-casio.com/Fr/programmes/' \
|
||||
f'voir_un_programme_casio.php?showid={program}'
|
||||
self._validate()
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,113 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from .. import BlockTag as _BlockTag
|
||||
|
||||
__all__ = ["ListTag", "ListElementTag"]
|
||||
|
||||
|
||||
# Bullet style names.
|
||||
|
||||
_ol_list_style_types = {
|
||||
'disc': 'disc',
|
||||
'circle': 'circle',
|
||||
'square': 'square',
|
||||
}
|
||||
_ul_list_style_types = {
|
||||
'1': 'decimal',
|
||||
'a': 'lower-alpha',
|
||||
'A': 'upper-alpha',
|
||||
'i': 'lower-roman',
|
||||
'I': 'upper-roman',
|
||||
}
|
||||
|
||||
_list_style_types = _ol_list_style_types.copy()
|
||||
_list_style_types.update(_ul_list_style_types)
|
||||
|
||||
_ul_lst_names = set(_ul_list_style_types.keys())
|
||||
_ol_lst_names = set(_ol_list_style_types.keys())
|
||||
|
||||
# Tag definitions.
|
||||
|
||||
class _ListTagBase(_BlockTag):
|
||||
pass
|
||||
|
||||
class ListElementTag(_BlockTag):
|
||||
""" List element for basic lists (see `ListTag`). """
|
||||
|
||||
aliases = ('[*]', '[li]')
|
||||
only_in = (_ListTagBase,)
|
||||
notempty = True
|
||||
superblock = True
|
||||
not_within_itself = True
|
||||
|
||||
def begin_html(self):
|
||||
return '<li>'
|
||||
|
||||
def end_html(self):
|
||||
return '</li>'
|
||||
|
||||
class ListTag(_ListTagBase):
|
||||
""" Main tag for making basic lists.
|
||||
Example use:
|
||||
|
||||
[ul]
|
||||
[*] Item number one.
|
||||
[*] Item number [b]two[/b].
|
||||
[/ul]
|
||||
"""
|
||||
|
||||
aliases = ('[list]', '[ul]', '[ol]')
|
||||
notempty = True
|
||||
superblock = True
|
||||
allowed_tags = (ListElementTag,)
|
||||
no_text = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
us = _ul_lst_names
|
||||
os = _ol_lst_names
|
||||
|
||||
if name == '[list]' and value == None:
|
||||
self._tag = 'ul'
|
||||
self._style = None
|
||||
elif name == '[list]' and value in us:
|
||||
self._tag = 'ul'
|
||||
self._style = value
|
||||
elif name == '[list]' and value in os:
|
||||
self._tag = 'ol'
|
||||
self._style = value
|
||||
elif name == '[ul]' and value == None:
|
||||
self._tag = 'ul'
|
||||
self._style = None
|
||||
elif name == '[ul]' and value in us:
|
||||
self._tag = 'ul'
|
||||
self._style = value
|
||||
elif name == '[ol]' and value == None:
|
||||
self._tag = 'ol'
|
||||
self._style = None
|
||||
elif name == '[ol]' and value in os:
|
||||
self._tag = 'ol'
|
||||
self._style = value
|
||||
else:
|
||||
raise ValueError("invalid bullet style")
|
||||
|
||||
# Find out the HTML style name.
|
||||
|
||||
if self._style != None:
|
||||
self._style = _list_style_types[self._style]
|
||||
|
||||
def begin_html(self):
|
||||
tag = f'<{self._tag}'
|
||||
if self._style != None:
|
||||
tag += f' style="list-style-type: {self._style}"'
|
||||
tag += '>'
|
||||
|
||||
return tag
|
||||
|
||||
def end_html(self):
|
||||
return '</ul>'
|
||||
|
||||
# End of file.
|
|
@ -4,14 +4,15 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutBlockTag as _TextoutBlockTag
|
||||
from .. import BlockTag as _BlockTag
|
||||
|
||||
__all__ = ["TextoutProgressTag"]
|
||||
__all__ = ["ProgressTag"]
|
||||
|
||||
class TextoutProgressTag(_TextoutBlockTag):
|
||||
|
||||
class ProgressTag(_BlockTag):
|
||||
""" Progress tag, used to display the progress on anything.
|
||||
Usage:
|
||||
|
||||
|
||||
[progress=50]My great progress bar[/progress]
|
||||
[progress=100][/progress] """
|
||||
|
||||
|
@ -28,11 +29,8 @@ class TextoutProgressTag(_TextoutBlockTag):
|
|||
|
||||
def end_html(self):
|
||||
return '' \
|
||||
'<div style="background-color: white; border: 1px solid black; ' \
|
||||
'width: 50%; margin-top: 2px; text-align: left;">' \
|
||||
'<div style="background-color: #FF3E28; color: black; ' \
|
||||
'font-weight: bold; max-width: 100%; width: {}%;' \
|
||||
'height: 18px;"> {}%' \
|
||||
'</div></div></div>'.format(self._val, self._val)
|
||||
'<div class="progress">' \
|
||||
'<div class="progress-inner" style="width: {}%;"> {}%' \
|
||||
'</div></div></div>'.format(self._val, self._val)
|
||||
|
||||
# End of file.
|
|
@ -4,16 +4,15 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutBlockTag as _TextoutBlockTag
|
||||
from html import escape as _htmlescape
|
||||
from ...smileys import htmlsmileys as _htmlsmileys
|
||||
from .. import BlockTag as _BlockTag
|
||||
|
||||
__all__ = ["TextoutQuoteTag"]
|
||||
__all__ = ["QuoteTag"]
|
||||
|
||||
class TextoutQuoteTag(_TextoutBlockTag):
|
||||
|
||||
class QuoteTag(_BlockTag):
|
||||
""" The main tag for quoting someone.
|
||||
Example uses:
|
||||
|
||||
|
||||
[quote]Hey, I said that![/quote]
|
||||
[quote=Someone important]I said something important, and it's
|
||||
multiline and [b]formatted[/b]!
|
||||
|
@ -23,6 +22,8 @@ class TextoutQuoteTag(_TextoutBlockTag):
|
|||
|
||||
aliases = ('[quote]',)
|
||||
superblock = True
|
||||
notempty = True
|
||||
procvalue = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
self._value = value
|
||||
|
@ -30,8 +31,7 @@ class TextoutQuoteTag(_TextoutBlockTag):
|
|||
def begin_html(self):
|
||||
f = '<div class="citation">'
|
||||
if self._value:
|
||||
f += '<p><b>{} a écrit :</b></p>' \
|
||||
.format(_htmlsmileys(_htmlescape(self._value)))
|
||||
f += '<p><b>{} a écrit :</b></p>'.format(self._value)
|
||||
return f
|
||||
|
||||
def end_html(self):
|
|
@ -4,18 +4,19 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutInlineTag as _TextoutInlineTag
|
||||
import string as _string
|
||||
from .. import InlineTag as _InlineTag
|
||||
|
||||
__all__ = ["TextoutRotTag"]
|
||||
__all__ = ["RotTag"]
|
||||
|
||||
class TextoutRotTag(_TextoutInlineTag):
|
||||
|
||||
class RotTag(_InlineTag):
|
||||
""" Tag which un-rot13 a content.
|
||||
Demonstration tag for content processing.
|
||||
Example uses:
|
||||
|
||||
[rot=13]obawbhe[/rot]
|
||||
[rot13]obawbhe[/rot13]
|
||||
[rot13]Obawbhe[/rot13]
|
||||
"""
|
||||
|
||||
aliases = ('[rot]', '[rot13]')
|
||||
|
@ -23,8 +24,11 @@ class TextoutRotTag(_TextoutInlineTag):
|
|||
|
||||
def prepare(self, name, value):
|
||||
if name == "[rot]":
|
||||
rot = int(value)
|
||||
assert 1 <= rot <= 25
|
||||
if not value:
|
||||
value = 13
|
||||
else:
|
||||
rot = int(value)
|
||||
assert 1 <= rot <= 25
|
||||
else:
|
||||
rot = int(name[4:-1])
|
||||
|
|
@ -4,20 +4,23 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutBlockTag as _TextoutBlockTag
|
||||
from html import escape as _htmlescape
|
||||
from .. import BlockTag as _BlockTag
|
||||
|
||||
__all__ = ["TextoutShowTag"]
|
||||
__all__ = ["ShowTag"]
|
||||
|
||||
class TextoutShowTag(_TextoutBlockTag):
|
||||
|
||||
class ShowTag(_BlockTag):
|
||||
""" Tag which shows the HTML code that is produced by textout().
|
||||
Example uses:
|
||||
|
||||
|
||||
[show][b]hello world![/show]
|
||||
"""
|
||||
|
||||
aliases = ('[show]',)
|
||||
notempty = True
|
||||
superblock = True
|
||||
inlined = True
|
||||
generic = False
|
||||
raw = False
|
||||
|
||||
|
@ -25,7 +28,7 @@ class TextoutShowTag(_TextoutBlockTag):
|
|||
return _htmlescape(content)
|
||||
|
||||
def begin_html(self):
|
||||
return '<span style="font-family: monospace;">'
|
||||
return '<span class="inline-code">'
|
||||
|
||||
def end_html(self):
|
||||
return '</span>'
|
|
@ -0,0 +1,171 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from .. import Smiley as _Smiley
|
||||
|
||||
__all__ = ["TwistedSmiley", "EvilSmiley", "SmileSmiley", "WinkSmiley",
|
||||
"SadSmiley", "GrinSmiley", "HeheSmiley", "CoolSmiley", "Cool2Smiley",
|
||||
"MadSmiley", "EekSmiley", "MrGreenSmiley", "ShockedSmiley",
|
||||
"ConfusedSmiley", "EyebrowsSmiley", "CrySmiley", "LolSmiley",
|
||||
"SorrySmiley", "RollEyesSmiley", "WazaSmiley", "HereSmiley",
|
||||
"BowSmiley", "GoodSmiley", "LoveSmiley", "OuchSmiley", "FacepalmSmiley",
|
||||
"InsultsSmiley", "WhatSmiley", "ExclSmiley"]
|
||||
|
||||
_prefix = '/images/smileys/'
|
||||
|
||||
|
||||
class TwistedSmiley(_Smiley):
|
||||
aliases = ('>:)',)
|
||||
url = _prefix + 'twisted.gif'
|
||||
|
||||
|
||||
class EvilSmiley(_Smiley):
|
||||
aliases = ('>:(', ':grr:')
|
||||
url = _prefix + 'evil.gif'
|
||||
|
||||
|
||||
class SmileSmiley(_Smiley):
|
||||
aliases = (':)',)
|
||||
url = _prefix + 'smile.gif'
|
||||
|
||||
|
||||
class WinkSmiley(_Smiley):
|
||||
aliases = (';)',)
|
||||
url = _prefix + 'wink.gif'
|
||||
|
||||
|
||||
class SadSmiley(_Smiley):
|
||||
aliases = (':(',)
|
||||
url = _prefix + 'sad.gif'
|
||||
|
||||
|
||||
class GrinSmiley(_Smiley):
|
||||
aliases = (':D', ':grin:')
|
||||
url = _prefix + 'grin.gif'
|
||||
|
||||
|
||||
class HeheSmiley(_Smiley):
|
||||
aliases = (':p',)
|
||||
url = _prefix + 'hehe.gif'
|
||||
|
||||
|
||||
class CoolSmiley(_Smiley):
|
||||
aliases = (':cool:',)
|
||||
url = _prefix + 'cool.gif'
|
||||
|
||||
|
||||
class Cool2Smiley(_Smiley):
|
||||
aliases = ('8-)',)
|
||||
url = _prefix + 'cool2.gif'
|
||||
|
||||
|
||||
class MadSmiley(_Smiley):
|
||||
aliases = (':@',)
|
||||
url = _prefix + 'mad.gif'
|
||||
|
||||
|
||||
class EekSmiley(_Smiley):
|
||||
aliases = ('0_0',)
|
||||
url = _prefix + 'eek.gif'
|
||||
|
||||
|
||||
class MrGreenSmiley(_Smiley):
|
||||
aliases = (':E', ':mrgreen:')
|
||||
url = _prefix + 'mrgreen.gif'
|
||||
|
||||
|
||||
class ShockedSmiley(_Smiley):
|
||||
aliases = (':O',)
|
||||
url = _prefix + 'shocked.gif'
|
||||
|
||||
|
||||
class ConfusedSmiley(_Smiley):
|
||||
aliases = (':s', ':oops:')
|
||||
url = _prefix + 'confused2.gif'
|
||||
|
||||
|
||||
class EyebrowsSmiley(_Smiley):
|
||||
aliases = ('^^',)
|
||||
url = _prefix + 'eyebrows.gif'
|
||||
|
||||
|
||||
class CrySmiley(_Smiley):
|
||||
aliases = (":'(", ":cry:")
|
||||
url = _prefix + 'cry.gif'
|
||||
|
||||
|
||||
# FIXME
|
||||
#class WhistleSmiley(_Smiley):
|
||||
# aliases = (":-°", ':whistle:')
|
||||
# url = _prefix + 'whistle.gif'
|
||||
# height = '15px'
|
||||
|
||||
|
||||
class LolSmiley(_Smiley):
|
||||
aliases = (":lol:",)
|
||||
url = _prefix + 'lol.gif'
|
||||
|
||||
|
||||
class SorrySmiley(_Smiley):
|
||||
aliases = (":sry:",)
|
||||
url = _prefix + 'redface.gif'
|
||||
|
||||
|
||||
class RollEyesSmiley(_Smiley):
|
||||
aliases = (":mmm:",)
|
||||
url = _prefix + 'rolleyes.gif'
|
||||
|
||||
|
||||
class WazaSmiley(_Smiley):
|
||||
aliases = (":waza:",)
|
||||
url = _prefix + 'waza.gif'
|
||||
|
||||
|
||||
class HereSmiley(_Smiley):
|
||||
aliases = (":here:", ":arrow:")
|
||||
url = _prefix + 'pointer.gif'
|
||||
|
||||
|
||||
class BowSmiley(_Smiley):
|
||||
aliases = (":bow:",)
|
||||
url = _prefix + 'bow.gif'
|
||||
|
||||
|
||||
class GoodSmiley(_Smiley):
|
||||
aliases = (":good:",)
|
||||
url = _prefix + 'welldone.gif'
|
||||
|
||||
|
||||
class LoveSmiley(_Smiley):
|
||||
aliases = (":love:",)
|
||||
url = _prefix + 'love.gif'
|
||||
|
||||
|
||||
class OuchSmiley(_Smiley):
|
||||
aliases = (":aie:",)
|
||||
url = _prefix + 'banghead2.gif'
|
||||
|
||||
|
||||
class FacepalmSmiley(_Smiley):
|
||||
aliases = (":facepalm:",)
|
||||
url = _prefix + 'facepalm.gif'
|
||||
|
||||
|
||||
class InsultsSmiley(_Smiley):
|
||||
aliases = (":argh:",)
|
||||
url = _prefix + 'insults.gif'
|
||||
|
||||
|
||||
class WhatSmiley(_Smiley):
|
||||
aliases = (":?:",)
|
||||
url = _prefix + 'what.gif'
|
||||
|
||||
|
||||
class ExclSmiley(_Smiley):
|
||||
aliases = (":!:",)
|
||||
url = _prefix + 'excl.gif'
|
||||
|
||||
# End of file.
|
|
@ -4,18 +4,17 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutBlockTag as _TextoutBlockTag
|
||||
from html import escape as _htmlescape
|
||||
from ...smileys import htmlsmileys as _htmlsmileys
|
||||
from .. import BlockTag as _BlockTag
|
||||
|
||||
__all__ = ["TextoutSpoilerTag"]
|
||||
__all__ = ["SpoilerTag"]
|
||||
|
||||
class TextoutSpoilerTag(_TextoutBlockTag):
|
||||
|
||||
class SpoilerTag(_BlockTag):
|
||||
""" Hide content at first glance, force people to click to read content.
|
||||
These elements can contain 'secret' elements such as solutions, source
|
||||
code, or various other things.
|
||||
Example uses:
|
||||
|
||||
|
||||
[spoiler]This is hidden![/spoiler]
|
||||
[spoiler=Y a quelque chose de caché !|Ah, bah en fait non :)]:E
|
||||
And it's multiline, [big]and formatted[/big], as usual :D[/spoiler]
|
||||
|
@ -23,6 +22,8 @@ class TextoutSpoilerTag(_TextoutBlockTag):
|
|||
|
||||
aliases = ('[spoiler]',)
|
||||
superblock = True
|
||||
notempty = True
|
||||
procvalue = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
self._closed = "Cliquez pour découvrir"
|
||||
|
@ -32,17 +33,16 @@ class TextoutSpoilerTag(_TextoutBlockTag):
|
|||
titles = value.split('|')
|
||||
if titles[0]:
|
||||
self._closed = titles[0]
|
||||
if len(titles) >= 2 and titles[1]:
|
||||
self._open = titles[1]
|
||||
if len(titles) >= 2 and (len(titles) > 2 or titles[1]):
|
||||
self._open = '|'.join(titles[1:])
|
||||
|
||||
def begin_html(self):
|
||||
return '<div class="spoiler">' \
|
||||
'<div class="title on" onclick="toggleSpoiler(this.parentNode, ' \
|
||||
'\'open\');">{}</div>' \
|
||||
'<div class="title off" onclick="toggleSpoiler(this.parentNode, ' \
|
||||
'\'close\');">{}</div>' \
|
||||
'<div class="off">'.format(_htmlsmileys(_htmlescape(self._closed)),
|
||||
_htmlsmileys(_htmlescape(self._open)))
|
||||
'<div class="title on" onclick="toggleSpoiler(this.parentNode, ' \
|
||||
'\'open\');"><p>{}</p></div>' \
|
||||
'<div class="title off" onclick="toggleSpoiler(this.parentNode, ' \
|
||||
'\'close\');"><p>{}</p></div>' \
|
||||
'<div class="off">'.format(self._closed, self._open)
|
||||
|
||||
def end_html(self):
|
||||
return '</div></div>'
|
|
@ -4,10 +4,13 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutInlineTag as _TextoutInlineTag
|
||||
from ...color import get_color
|
||||
from .. import InlineTag as _InlineTag
|
||||
from thcolor import Color as _Color
|
||||
|
||||
__all__ = ["TextoutTextTag"]
|
||||
__all__ = ["TextTag"]
|
||||
|
||||
def _get_color(text):
|
||||
return _Color.from_text(text).rgba()
|
||||
|
||||
# ---
|
||||
# Data.
|
||||
|
@ -30,10 +33,11 @@ _fonts = {
|
|||
# Tag definition.
|
||||
# ---
|
||||
|
||||
class TextoutTextTag(_TextoutInlineTag):
|
||||
|
||||
class TextTag(_InlineTag):
|
||||
""" Main tag for setting text formatting.
|
||||
Example uses:
|
||||
|
||||
|
||||
[b]Bold text.[/b]
|
||||
[i]Italic text.[/i]
|
||||
[u]Underlined text.[/u]
|
||||
|
@ -45,28 +49,31 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
[color=blue]This as well[/color]
|
||||
[color=rgb(255, 255, 255, 0.4)]BLACKNESS[/color]
|
||||
[color=hsl(0, 100%, 0.5)]This will be red.[/color]
|
||||
|
||||
|
||||
Also supports a hack used on Planète Casio for a while, which
|
||||
is a CSS injection, e.g.:
|
||||
|
||||
|
||||
[color=brown; size: 16pt]Hello world![/color]
|
||||
"""
|
||||
|
||||
aliases = ('[b]', '[i]', '[u]', '[s]', '[strike]',
|
||||
'[monospace]', '[mono]', '[font]', '[color]',
|
||||
aliases = ('[css]', '[b]', '[i]', '[u]', '[o]', '[s]', '[strike]',
|
||||
'[monospace]', '[mono]', '[font]', '[color]', '[c]',
|
||||
'[size]', '[big]', '[small]',
|
||||
'[arial]', '[comic]', '[tahoma]', '[courier]',
|
||||
'[haettenschweiler]', '[red]', '[green]', '[blue]',
|
||||
'[yellow]', '[maroon]', '[purple]', '[gray]',
|
||||
'[grey]', '[brown]')
|
||||
notempty = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
self._bold = False
|
||||
self._italic = False
|
||||
self._underline = False
|
||||
self._overline = False
|
||||
self._strike = False
|
||||
self._font = None
|
||||
self._color = None
|
||||
self._bgcolor = None
|
||||
self._size = None
|
||||
|
||||
# Récupérer la partie correspondant à l'injection CSS s'il y
|
||||
|
@ -74,7 +81,7 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
|
||||
def get_props(value):
|
||||
props = ''
|
||||
if value != None:
|
||||
if value is not None:
|
||||
index = value.find(';')
|
||||
if index >= 0:
|
||||
props = value[index + 1:]
|
||||
|
@ -85,17 +92,24 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
|
||||
name = name[1:-1]
|
||||
props = ""
|
||||
if name == "b":
|
||||
if name == "css":
|
||||
props = value
|
||||
elif name == "b":
|
||||
self._bold = True
|
||||
elif name == "i":
|
||||
self._italic = True
|
||||
elif name == "u":
|
||||
self._underline = True
|
||||
elif name == "o":
|
||||
self._overline = True
|
||||
elif name in ("s", "strike", "striked"):
|
||||
self._strike = True
|
||||
elif name == "color":
|
||||
elif name in ("color", 'c'):
|
||||
value, props = get_props(value)
|
||||
self._color = get_color(value)
|
||||
self._color = _get_color(value)
|
||||
elif name == 'f':
|
||||
value, props = get_props(value)
|
||||
self._bgcolor = _get_color(value)
|
||||
elif name == "font":
|
||||
value, props = get_props(value)
|
||||
assert value in _fonts
|
||||
|
@ -116,9 +130,9 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
elif name in _fonts:
|
||||
self._font = name
|
||||
else:
|
||||
self._color = get_color(name)
|
||||
self._color = _get_color(name)
|
||||
|
||||
# Gestion des injections CSS.
|
||||
# Gestion des propriétés CSS (par injection ou via `[css]`).
|
||||
|
||||
for prop in props.split(';'):
|
||||
prop = prop.strip()
|
||||
|
@ -131,7 +145,9 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
name = name.strip()
|
||||
value = ':'.join(value).strip()
|
||||
|
||||
if name == 'size':
|
||||
if name in ('size', 'font-size'):
|
||||
# Control the font size.
|
||||
|
||||
unit = 'pt'
|
||||
if value.endswith('pt'):
|
||||
value = value[:-2].rstrip()
|
||||
|
@ -141,7 +157,7 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
|
||||
if not value or \
|
||||
any(c != '0' for c in value[:-3]) or \
|
||||
any(not c in '0123456789' for c in value[-3:]):
|
||||
any(c not in '0123456789' for c in value[-3:]):
|
||||
continue
|
||||
|
||||
value = int(value[-3:])
|
||||
|
@ -150,6 +166,14 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
|
||||
if 0 < value <= 3.0:
|
||||
self._size = value
|
||||
elif name == 'color':
|
||||
# Control the text color.
|
||||
|
||||
self._color = _get_color(value)
|
||||
elif name == 'background-color':
|
||||
# Control the background color.
|
||||
|
||||
self._bgcolor = _get_color(value)
|
||||
|
||||
def _get_css(self):
|
||||
""" Get the `style` CSS classes and properties for HTML output. """
|
||||
|
@ -161,10 +185,15 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
props.append('font-weight: bold')
|
||||
if self._italic:
|
||||
props.append('font-style: italic')
|
||||
if self._underline or self._strike:
|
||||
props.append('text-decoration:{}{}'.format(' underline' \
|
||||
if self._underline else '', ' line-through' \
|
||||
if self._strike else ''))
|
||||
if self._underline or self._strike or self._overline:
|
||||
props.append('text-decoration:{}{}{}'.format(' underline'
|
||||
if self._underline else '', ' line-through'
|
||||
if self._strike else '', ' overline'
|
||||
if self._overline else ''))
|
||||
else:
|
||||
if self._overline:
|
||||
props.append('text-decoration:{}'.format(' overline'
|
||||
if self._overline else ''))
|
||||
|
||||
if self._font:
|
||||
props.append('font-family: ' + self._font)
|
||||
|
@ -181,9 +210,15 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
|
||||
props.append('color: #%02X%02X%02X' % self._color[0:3])
|
||||
if self._color[3] < 1.0:
|
||||
props.append('color: rgba({}, {}, {}, {})' \
|
||||
props.append('color: rgba({}, {}, {}, {})'
|
||||
.format(*self._color))
|
||||
|
||||
if self._bgcolor and self._bgcolor[3] != 0.0:
|
||||
props.append('background-color: #%02X%02X%02X' % self._color[0:3])
|
||||
if self._bgcolor[3] < 1.0:
|
||||
props.append('background-color: rgba({}, {}, {}, {})'
|
||||
.format(*self._bgcolor))
|
||||
|
||||
if self._size:
|
||||
props.append('font-size: {}em'.format(self._size))
|
||||
|
||||
|
@ -194,8 +229,8 @@ class TextoutTextTag(_TextoutInlineTag):
|
|||
|
||||
cls, props = self._get_css()
|
||||
if cls or props:
|
||||
props = '<span{}{}>'.format(' class="{}"'.format(' '.join(cls)) \
|
||||
if cls else '', ' style="{}"'.format('; '.join(props)) \
|
||||
props = '<span{}{}>'.format(' class="{}"'.format(' '.join(cls))
|
||||
if cls else '', ' style="{}"'.format('; '.join(props))
|
||||
if props else '')
|
||||
else:
|
||||
props = ''
|
|
@ -4,14 +4,15 @@
|
|||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutBlockTag as _TextoutBlockTag
|
||||
from .. import BlockTag as _BlockTag
|
||||
|
||||
__all__ = ["TextoutTitleTag"]
|
||||
__all__ = ["TitleTag"]
|
||||
|
||||
class TextoutTitleTag(_TextoutBlockTag):
|
||||
|
||||
class TitleTag(_BlockTag):
|
||||
""" The title tag.
|
||||
Example uses:
|
||||
|
||||
|
||||
[title]Some title[/title]
|
||||
[subtitle]Some subtitle[/subtitle]
|
||||
"""
|
||||
|
@ -20,13 +21,26 @@ class TextoutTitleTag(_TextoutBlockTag):
|
|||
raw = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
level = self.tweak("title_level", "1")
|
||||
if level[0] == "h":
|
||||
level = level[1:]
|
||||
level = int(level)
|
||||
assert 1 <= level <= 5
|
||||
|
||||
# Name.
|
||||
|
||||
self._level = name[1:-1]
|
||||
|
||||
# HTML tag.
|
||||
|
||||
level += self._level == "subtitle"
|
||||
self._tag = f"h{level}"
|
||||
|
||||
def begin_html(self):
|
||||
return ('<h5>', '<h4>')[self._level == "title"]
|
||||
return f'<{self._tag} class="{self._level}">'
|
||||
|
||||
def end_html(self):
|
||||
return ('</h5>', '</h4>')[self._level == "title"]
|
||||
return f'</{self._tag}>'
|
||||
|
||||
def begin_lightscript(self):
|
||||
return '#' * ((self._level == "subtitle") + 1) + ' '
|
|
@ -0,0 +1,138 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
import urllib.parse as _urlparse
|
||||
from html import escape as _htmlescape
|
||||
|
||||
from .. import BlockTag as _BlockTag
|
||||
|
||||
__all__ = ["VideoTag", "YoutubeTag"]
|
||||
|
||||
_defaultratio_w = 16
|
||||
_defaultratio_h = 9
|
||||
|
||||
|
||||
class VideoTag(_BlockTag):
|
||||
""" The video tag, puts a preview of the video whose URL is given.
|
||||
Only a few 'big' services are supported for now.
|
||||
Example uses:
|
||||
|
||||
[video]video_url[/video]
|
||||
[video=4:3]video_url[/video]
|
||||
[video tiny]video_url[/video tiny]
|
||||
[video]https://www.youtube.com/watch?v=yhXpV8hRKxQ[/video]
|
||||
"""
|
||||
|
||||
aliases = ('[video]', '[video tiny]')
|
||||
raw = True
|
||||
noinline = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
""" Prepare the video tag. """
|
||||
|
||||
_align = {
|
||||
'center': ('center', False),
|
||||
'centre': ('center', False),
|
||||
'left': ('left', False),
|
||||
'gauche': ('left', False),
|
||||
'right': ('right', False),
|
||||
'droite': ('right', False),
|
||||
'float': (None, True),
|
||||
'floating': (None, True),
|
||||
'flotte': (None, True),
|
||||
'flottant': (None, True),
|
||||
'float-left': ('left', True),
|
||||
'float-center': ('center', True),
|
||||
'float-centre': ('center', True),
|
||||
'float-right': ('right', True),
|
||||
}
|
||||
|
||||
self._sizeclass = "video-tiny" if "tiny" in name \
|
||||
else None
|
||||
self._align = None
|
||||
self._float = False
|
||||
self._ratio = None
|
||||
|
||||
for arg in map(str.strip, (value or "").split('|')):
|
||||
if not arg:
|
||||
pass
|
||||
elif arg[0] in '0123456789:':
|
||||
rx, ry = _defaultratio_w, _defaultratio_h
|
||||
rn = 0
|
||||
rat = arg.split(':')
|
||||
|
||||
try: rx = int(rat[0]); rn += 1
|
||||
except: pass
|
||||
try: ry = int(rat[1]); rn += 1
|
||||
except: pass
|
||||
|
||||
if rn:
|
||||
self._ratio = round(ry / rx, 4)
|
||||
elif arg in _align:
|
||||
al, fl = _align[arg]
|
||||
if al != None:
|
||||
self._align = al
|
||||
if fl:
|
||||
self._float = True
|
||||
|
||||
def preprocess(self, content):
|
||||
try:
|
||||
self._video = self.video(content)
|
||||
except:
|
||||
url = _urlparse.urlparse(content)
|
||||
if url.scheme not in ('http', 'https'):
|
||||
raise Exception("No allowed prefix!")
|
||||
|
||||
self._video = content
|
||||
|
||||
def content_html(self):
|
||||
""" Produce the embed code for the given type. """
|
||||
|
||||
if isinstance(self._video, str):
|
||||
url = _htmlescape(self._video)
|
||||
return '<p><a href="{}">{}</a></p>'.format(url, url)
|
||||
|
||||
align = "float-" + (self._align or "left") if self._align \
|
||||
else self._align
|
||||
|
||||
if self._ratio:
|
||||
ratio = self._ratio * 100
|
||||
elif hasattr(self._video, 'ratio'):
|
||||
ratio = self._video.ratio * 100
|
||||
else:
|
||||
ratio = round(_defaultratio_h / _defaultratio_w, 4) * 100
|
||||
iratio = int(ratio)
|
||||
if ratio == iratio:
|
||||
ratio = iratio
|
||||
ratio = str(ratio)
|
||||
|
||||
code = '<div class="video-wrapper{}{}"{}>' \
|
||||
.format(f" {self._sizeclass}" if self._sizeclass else "",
|
||||
f' img-{align}' if align else "",
|
||||
f' style="padding-bottom: {ratio}%"')
|
||||
|
||||
code += '<iframe src="{}" frameborder="0" allowfullscreen>' \
|
||||
'</iframe>'.format(self._video.embed)
|
||||
|
||||
return code + '</div>'
|
||||
|
||||
def content_lightscript(self):
|
||||
url = self._url.replace('[', '%5B').replace(']', '%5D')
|
||||
return '[[image:{}]]'.format(url)
|
||||
|
||||
|
||||
class YoutubeTag(VideoTag):
|
||||
""" Alias for the video tag with only the Youtube possibility.
|
||||
Example uses:
|
||||
|
||||
[youtube]okMK1NYRySI[/youtube] """
|
||||
|
||||
aliases = ('[youtube]',)
|
||||
|
||||
def preprocess(self, content):
|
||||
super().preprocess(f'https://www.youtube.com/watch?v={content}')
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
import re as _re
|
||||
import urllib.parse as _urlparse
|
||||
|
||||
from .. import Video as _Video
|
||||
|
||||
__all__ = ["YouTubeVideo", "DailymotionVideo", "VimeoVideo"]
|
||||
|
||||
|
||||
class YouTubeVideo(_Video):
|
||||
""" Get a video from Youtube. """
|
||||
|
||||
_hexcode = _re.compile('[a-zA-Z0-9_-]+')
|
||||
|
||||
def __init__(self, url):
|
||||
url = _urlparse.urlparse(url)
|
||||
if url.scheme not in ('http', 'https'):
|
||||
raise Exception
|
||||
|
||||
if url.netloc == "youtu.be":
|
||||
self._id = url.path[1:]
|
||||
if not self._hexcode.match(self._id):
|
||||
raise ValueError("invalid id")
|
||||
elif url.netloc in ('youtube.com', 'www.youtube.com'):
|
||||
if url.path != '/watch':
|
||||
raise ValueError("invalid id")
|
||||
self._id = _urlparse.parse_qs(url.query)['v'][0]
|
||||
if not self._hexcode.fullmatch(self._id):
|
||||
raise Exception
|
||||
else:
|
||||
raise ValueError("unknown URL")
|
||||
|
||||
self.embed = f"https://www.youtube.com/embed/{self._id}"
|
||||
|
||||
|
||||
class DailymotionVideo(_Video):
|
||||
""" Get a video from Dailymotion. """
|
||||
|
||||
_dailypath = _re.compile('^/video/([a-z0-9]+)$')
|
||||
|
||||
def __init__(self, url):
|
||||
url = _urlparse.urlparse(url)
|
||||
if url.scheme not in ('http', 'https'):
|
||||
raise Exception
|
||||
|
||||
if url.netloc in ('dailymotion.com', 'www.dailymotion.com'):
|
||||
self._code = self._dailypath.match(url.path).groups()[0]
|
||||
else:
|
||||
raise ValueError("unknown URL")
|
||||
|
||||
self.embed = f"https://www.dailymotion.com/embed/video/{self._code}"
|
||||
|
||||
|
||||
class VimeoVideo(_Video):
|
||||
""" Get a video from Vimeo. """
|
||||
|
||||
_numcode = _re.compile('^/[0-9]+$')
|
||||
|
||||
def __init__(self, url):
|
||||
url = _urlparse.urlparse(url)
|
||||
if url.scheme not in ('http', 'https'):
|
||||
raise Exception
|
||||
|
||||
if url.netloc in ('vimeo.com', 'www.vimeo.com'):
|
||||
self._code = url.path[1:]
|
||||
if not self._numcode.match(self._code):
|
||||
raise ValueError("invalid video code")
|
||||
else:
|
||||
raise ValueError("unknown URL")
|
||||
|
||||
self.embed = f"https://player.vimeo.com/video/{self._code}" \
|
||||
"?title=0&byline=0&portrait=0"
|
||||
|
||||
# WARNING: This is only for demonstration sake. Do not use without a cache!
|
||||
# This demonstration class uses the `embed-python` module.
|
||||
#
|
||||
#from embed import Embed as _Embed
|
||||
#
|
||||
#class OpenWebVideo(_Video):
|
||||
# """ Decentralized way to gather a video data. """
|
||||
#
|
||||
# def __init__(self, url):
|
||||
# u = _urlparse.urlparse(url)
|
||||
# if not u.scheme in ('https',):
|
||||
# raise Exception
|
||||
#
|
||||
# embed = _Embed(url)
|
||||
# embed = embed.embed
|
||||
# assert embed['type'] == 'video'
|
||||
#
|
||||
# self.embed = embed['url']
|
||||
# if 'ratio' in embed:
|
||||
# self.ratio = embed['ratio'] / 100
|
||||
|
||||
# End of file.
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Built-in tags and smileys for the `textoutpc` module.
|
||||
|
||||
Some of these options will probably have to move to a separate module
|
||||
Planète Casio-specific, but still, here we are.
|
||||
"""
|
||||
|
||||
# Tags.
|
||||
|
||||
from ._Align import *
|
||||
from ._Code import *
|
||||
from ._Image import *
|
||||
from ._Label import *
|
||||
from ._Link import *
|
||||
from ._List import *
|
||||
from ._Progress import *
|
||||
from ._Quote import *
|
||||
from ._Rot import *
|
||||
from ._Show import *
|
||||
from ._Spoiler import *
|
||||
from ._Text import *
|
||||
from ._Title import *
|
||||
from ._Video import *
|
||||
|
||||
# Other resources (smileys, multimedia).
|
||||
|
||||
from ._Smileys import *
|
||||
from ._Images import *
|
||||
from ._Videos import *
|
||||
|
||||
# End of file.
|
|
@ -1,168 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Named colors definitions. Color names are case-insensitive.
|
||||
Taken from: https://www.w3schools.com/cssref/css_colors.asp """
|
||||
|
||||
__all__ = ["colors"]
|
||||
|
||||
colors = {
|
||||
# Standard CSS3 named colors, ordered by hex code.
|
||||
|
||||
'black': '#000000',
|
||||
'navy': '#000080',
|
||||
'darkblue': '#00008B',
|
||||
'mediumblue': '#0000CD',
|
||||
'blue': '#0000FF',
|
||||
'darkgreen': '#006400',
|
||||
'green': '#008000',
|
||||
'teal': '#008080',
|
||||
'darkcyan': '#008B8B',
|
||||
'deepskyblue': '#00BFFF',
|
||||
'darkturquoise': '#00CED1',
|
||||
'mediumspringgreen': '#00FA9A',
|
||||
'lime': '#00FF00',
|
||||
'springgreen': '#00FF7F',
|
||||
'aqua': '#00FFFF',
|
||||
'cyan': '#00FFFF',
|
||||
'midnightblue': '#191970',
|
||||
'dodgerblue': '#1E90FF',
|
||||
'lightseagreen': '#20B2AA',
|
||||
'forestgreen': '#228B22',
|
||||
'seagreen': '#2E8B57',
|
||||
'darkslategray': '#2F4F4F',
|
||||
'darkslategrey': '#2F4F4F',
|
||||
'limegreen': '#32CD32',
|
||||
'mediumseagreen': '#3CB371',
|
||||
'turquoise': '#40E0D0',
|
||||
'royalblue': '#4169E1',
|
||||
'steelblue': '#4682B4',
|
||||
'darkslateblue': '#483D8B',
|
||||
'mediumturquoise': '#48D1CC',
|
||||
'indigo': '#4B0082',
|
||||
'darkolivegreen': '#556B2F',
|
||||
'cadetblue': '#5F9EA0',
|
||||
'cornflowerblue': '#6495ED',
|
||||
'rebeccapurple': '#663399',
|
||||
'mediumaquamarine': '#66CDAA',
|
||||
'dimgray': '#696969',
|
||||
'dimgrey': '#696969',
|
||||
'slateblue': '#6A5ACD',
|
||||
'olivedrab': '#6B8E23',
|
||||
'slategray': '#708090',
|
||||
'slategrey': '#708090',
|
||||
'lightslategray': '#778899',
|
||||
'lightslategrey': '#778899',
|
||||
'mediumslateblue': '#7B68EE',
|
||||
'lawngreen': '#7CFC00',
|
||||
'chartreuse': '#7FFF00',
|
||||
'aquamarine': '#7FFFD4',
|
||||
'maroon': '#800000',
|
||||
'purple': '#800080',
|
||||
'olive': '#808000',
|
||||
'gray': '#808080',
|
||||
'grey': '#808080',
|
||||
'skyblue': '#87CEEB',
|
||||
'lightskyblue': '#87CEFA',
|
||||
'blueviolet': '#8A2BE2',
|
||||
'darkred': '#8B0000',
|
||||
'darkmagenta': '#8B008B',
|
||||
'saddlebrown': '#8B4513',
|
||||
'darkseagreen': '#8FBC8F',
|
||||
'lightgreen': '#90EE90',
|
||||
'mediumpurple': '#9370DB',
|
||||
'darkviolet': '#9400D3',
|
||||
'palegreen': '#98FB98',
|
||||
'darkorchid': '#9932CC',
|
||||
'yellowgreen': '#9ACD32',
|
||||
'sienna': '#A0522D',
|
||||
'brown': '#A52A2A',
|
||||
'darkgray': '#A9A9A9',
|
||||
'darkgrey': '#A9A9A9',
|
||||
'lightblue': '#ADD8E6',
|
||||
'greenyellow': '#ADFF2F',
|
||||
'paleturquoise': '#AFEEEE',
|
||||
'lightsteelblue': '#B0C4DE',
|
||||
'powderblue': '#B0E0E6',
|
||||
'firebrick': '#B22222',
|
||||
'darkgoldenrod': '#B8860B',
|
||||
'mediumorchid': '#BA55D3',
|
||||
'rosybrown': '#BC8F8F',
|
||||
'darkkhaki': '#BDB76B',
|
||||
'silver': '#C0C0C0',
|
||||
'mediumvioletred': '#C71585',
|
||||
'indianred': '#CD5C5C',
|
||||
'peru': '#CD853F',
|
||||
'chocolate': '#D2691E',
|
||||
'tan': '#D2B48C',
|
||||
'lightgray': '#D3D3D3',
|
||||
'lightgrey': '#D3D3D3',
|
||||
'thistle': '#D8BFD8',
|
||||
'orchid': '#DA70D6',
|
||||
'goldenrod': '#DAA520',
|
||||
'palevioletred': '#DB7093',
|
||||
'crimson': '#DC143C',
|
||||
'gainsboro': '#DCDCDC',
|
||||
'plum': '#DDA0DD',
|
||||
'burlywood': '#DEB887',
|
||||
'lightcyan': '#E0FFFF',
|
||||
'lavender': '#E6E6FA',
|
||||
'darksalmon': '#E9967A',
|
||||
'violet': '#EE82EE',
|
||||
'palegoldenrod': '#EEE8AA',
|
||||
'lightcoral': '#F08080',
|
||||
'khaki': '#F0E68C',
|
||||
'aliceblue': '#F0F8FF',
|
||||
'honeydew': '#F0FFF0',
|
||||
'azure': '#F0FFFF',
|
||||
'sandybrown': '#F4A460',
|
||||
'wheat': '#F5DEB3',
|
||||
'beige': '#F5F5DC',
|
||||
'whitesmoke': '#F5F5F5',
|
||||
'mintcream': '#F5FFFA',
|
||||
'ghostwhite': '#F8F8FF',
|
||||
'salmon': '#FA8072',
|
||||
'antiquewhite': '#FAEBD7',
|
||||
'linen': '#FAF0E6',
|
||||
'lightgoldenrodyellow': '#FAFAD2',
|
||||
'oldlace': '#FDF5E6',
|
||||
'red': '#FF0000',
|
||||
'magenta': '#FF00FF',
|
||||
'fuchsia': '#FF00FF',
|
||||
'deeppink': '#FF1493',
|
||||
'orangered': '#FF4500',
|
||||
'tomato': '#FF6347',
|
||||
'hotpink': '#FF69B4',
|
||||
'coral': '#FF7F50',
|
||||
'darkorange': '#FF8C00',
|
||||
'lightsalmon': '#FFA07A',
|
||||
'orange': '#FFA500',
|
||||
'lightpink': '#FFB6C1',
|
||||
'pink': '#FFC0CB',
|
||||
'gold': '#FFD700',
|
||||
'peachpuff': '#FFDAB9',
|
||||
'navajowhite': '#FFDEAD',
|
||||
'moccasin': '#FFE4B5',
|
||||
'bisque': '#FFE4C4',
|
||||
'mistyrose': '#FFE4E1',
|
||||
'blanchedalmond': '#FFEBCD',
|
||||
'papayawhip': '#FFEFD5',
|
||||
'lavenderblush': '#FFF0F5',
|
||||
'seashell': '#FFF5EE',
|
||||
'cornsilk': '#FFF8DC',
|
||||
'lemonchiffon': '#FFFACD',
|
||||
'floralwhite': '#FFFAF0',
|
||||
'snow': '#FFFAFA',
|
||||
'yellow': '#FFFF00',
|
||||
'lightyellow': '#FFFFE0',
|
||||
'ivory': '#FFFFF0',
|
||||
'white': '#FFFFFF',
|
||||
|
||||
# Keyword/special named color.
|
||||
|
||||
'transparent': 'rgba(0,0,0,0)',
|
||||
}
|
||||
|
||||
# End of file.
|
|
@ -1,257 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" HTML/CSS like color parsing, mainly for the `[color]` tag.
|
||||
Defines the `get_color()` function which returns an rgba value.
|
||||
"""
|
||||
|
||||
import re as _re
|
||||
import math as _math
|
||||
|
||||
from .named import colors as _named_colors
|
||||
from .sys import hls_to_rgb as _hls_to_rgb, hwb_to_rgb as _hwb_to_rgb
|
||||
|
||||
__all__ = ["get_color"]
|
||||
|
||||
_cr = _re.compile("""
|
||||
rgba?\s*\(
|
||||
\s* (?P<rgb_r>[0-9]{1,3}) \s* ([,\\s]
|
||||
\s* (?P<rgb_g>[0-9]{1,3}) \s* ([,\\s]
|
||||
\s* (?P<rgb_b>[0-9]{1,3}) \s* ([,\\s]
|
||||
\s* ((?P<rgb_a_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<rgb_a_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*
|
||||
)?)?)?
|
||||
\)|
|
||||
hsla?\s*\(
|
||||
\s* (?P<hsl_hue>-? ([0-9]+\.?|[0-9]*\.[0-9]+) )
|
||||
(?P<hsl_agl>deg|grad|rad|turn|) \s*[,\\s]
|
||||
\s* ((?P<hsl_sat_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<hsl_sat_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*[,\\s]
|
||||
\s* ((?P<hsl_lgt_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<hsl_lgt_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*([,\\s/]
|
||||
\s* ((?P<hsl_aph_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<hsl_aph_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*
|
||||
)?
|
||||
\)|
|
||||
hlsa?\s*\(
|
||||
\s* (?P<hls_hue>-? ([0-9]+\.?|[0-9]*\.[0-9]+) )
|
||||
(?P<hls_agl>deg|grad|rad|turn|) \s*[,\\s]
|
||||
\s* ((?P<hls_lgt_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<hls_lgt_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*[,\\s]
|
||||
\s* ((?P<hls_sat_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<hls_sat_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*([,\\s/]
|
||||
\s* ((?P<hls_aph_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<hls_aph_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*)?
|
||||
\)|
|
||||
hwb\s*\(
|
||||
\s* (?P<hwb_hue>-? ([0-9]+\.?|[0-9]*\.[0-9]+) )
|
||||
(?P<hwb_agl>deg|grad|rad|turn|) \s*[,\\s]
|
||||
\s* ((?P<hwb_wht_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<hwb_wht_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*[,\\s]
|
||||
\s* ((?P<hwb_blk_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<hwb_blk_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*([,\\s/]
|
||||
\s* ((?P<hwb_aph_per> ([0-9]+\.?|[0-9]*\.[0-9]+) ) \s*%
|
||||
|(?P<hwb_aph_flt> (0*[01]\.?|0*\.[0-9]+) )) \s*)?
|
||||
\)|
|
||||
\# (?P<hex_digits> [0-9a-f]+)
|
||||
|
|
||||
(?P<legacy_chars> [0-9a-z]+)
|
||||
""", _re.VERBOSE | _re.I | _re.M)
|
||||
|
||||
def get_color(value):
|
||||
""" Get a color from a string.
|
||||
Returns an (r, g, b, a) color.
|
||||
Raises an exception if the color could not be read. """
|
||||
|
||||
# Check if is a color name.
|
||||
value = value.strip()
|
||||
try: value = _named_colors[value.lower()]
|
||||
except: pass
|
||||
|
||||
# Initialize the alpha.
|
||||
alpha = 1.0
|
||||
|
||||
# Get the match.
|
||||
match = _cr.fullmatch(value).groupdict()
|
||||
if match['hex_digits'] or match['legacy_chars']:
|
||||
# Imitate the Netscape behaviour. Find more about this here:
|
||||
# https://stackoverflow.com/a/8333464
|
||||
#
|
||||
# I've also extended the thing as I could to introduce more
|
||||
# modern syntaxes described on the dedicated MDN page:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
|
||||
#
|
||||
# First of all, depending on our source, we will act differently:
|
||||
# - if we are using the `hex_digits` source, then we use the modern
|
||||
# behaviour and do the fancy things such as `#ABC -> #AABBCC`
|
||||
# management and possible alpha decoding;
|
||||
# - if we are using the `legacy_chars` source, then we sanitize our
|
||||
# input by replacing invalid characters by '0' characters (the
|
||||
# 0xFFFF limit is due to how UTF-16 was managed at the time).
|
||||
# We shall also truncate our input to 128 characters.
|
||||
#
|
||||
# After these sanitization options, we will keep the same method as
|
||||
# for legacy color decoding. It should work and be tolerant enough…
|
||||
|
||||
members = 3
|
||||
if match['hex_digits']:
|
||||
hx = match['hex_digits'].lower()
|
||||
|
||||
# RGB and RGBA (3 and 4 char.) notations.
|
||||
if len(hx) in (3, 4):
|
||||
hx = hx[0:1] * 2 + hx[1:2] * 2 + hx[2:3] * 2 + hx[3:4] * 2
|
||||
|
||||
# Check if there is transparency or not.
|
||||
if len(hx) % 3 != 0 and len(hx) % 4 == 0:
|
||||
members = 4
|
||||
|
||||
else: # our source is `legacy_chars`
|
||||
hx = match['legacy_chars'].lower()
|
||||
hx = ''.join(c if c in '0123456789abcdef' \
|
||||
else ('0', '00')[ord(c) > 0xFFFF] for c in hx[:128])[:128]
|
||||
|
||||
# First, calculate some values we're going to need.
|
||||
# `iv` is the size of the zone for a member.
|
||||
# `sz` is the size of the digits slice to take in that zone (max. 8).
|
||||
# `of` is the offset in the zone of the slice to take.
|
||||
|
||||
iv = _math.ceil(len(hx) / members)
|
||||
of = iv - 8 if iv > 8 else 0
|
||||
sz = iv - of
|
||||
|
||||
# Then isolate the slices using the values calculated above.
|
||||
# `gr` will be an array of 3 or 4 digit strings (depending on the
|
||||
# number of members).
|
||||
|
||||
gr = list(map(lambda i: hx[i * iv + of:i * iv + iv] \
|
||||
.ljust(sz, '0'), range(members)))
|
||||
|
||||
# Check how many digits we can skip at the beginning of each slice.
|
||||
|
||||
pre = min(map(lambda x: len(x) - len(x.lstrip('0')), gr))
|
||||
pre = min(pre, sz - 2)
|
||||
|
||||
# Then extract the values.
|
||||
|
||||
it = map(lambda x: int('0' + x[pre:pre + 2], 16), gr)
|
||||
if members == 3:
|
||||
r, g, b = it
|
||||
else:
|
||||
r, g, b, alpha = it
|
||||
alpha /= 255.0
|
||||
elif match['rgb_r']:
|
||||
# Extract the values.
|
||||
|
||||
r = int(match['rgb_r'])
|
||||
g = int(match['rgb_g']) if match['rgb_g'] else 0
|
||||
b = int(match['rgb_b']) if match['rgb_b'] else 0
|
||||
|
||||
if match['rgb_a_per']:
|
||||
alpha = float(match['rgb_a_per']) / 100.0
|
||||
elif match['rgb_a_flt']:
|
||||
alpha = float(match['rgb_a_flt'])
|
||||
elif match['hsl_hue'] or match['hls_hue']:
|
||||
# Extract the values.
|
||||
|
||||
if match['hsl_hue']:
|
||||
hue = float(match['hsl_hue'])
|
||||
agl = match['hsl_agl']
|
||||
|
||||
# Saturation.
|
||||
if match['hsl_sat_per']:
|
||||
sat = float(match['hsl_sat_per']) / 100.0
|
||||
else:
|
||||
sat = float(match['hsl_sat_flt'])
|
||||
if sat > 1.0:
|
||||
sat /= 100.0
|
||||
|
||||
# Light.
|
||||
if match['hsl_lgt_per']:
|
||||
lgt = float(match['hsl_lgt_per']) / 100.0
|
||||
else:
|
||||
lgt = float(match['hsl_lgt_flt'])
|
||||
if lgt > 1.0:
|
||||
lgt /= 100.0
|
||||
|
||||
# Alpha value.
|
||||
if match['hsl_aph_per']:
|
||||
alpha = float(match['hsl_aph_per']) / 100.0
|
||||
elif match['hsl_aph_flt']:
|
||||
alpha = float(match['hsl_aph_flt'])
|
||||
else:
|
||||
hue = float(match['hls_hue'])
|
||||
agl = match['hls_agl']
|
||||
|
||||
# Saturation.
|
||||
if match['hls_sat_per']:
|
||||
sat = float(match['hls_sat_per']) / 100.0
|
||||
else:
|
||||
sat = float(match['hls_sat_flt'])
|
||||
|
||||
# Light.
|
||||
if match['hls_lgt_per']:
|
||||
lgt = float(match['hls_lgt_per']) / 100.0
|
||||
else:
|
||||
lgt = float(match['hls_lgt_flt'])
|
||||
|
||||
# Alpha value.
|
||||
if match['hls_aph_per']:
|
||||
alpha = float(match['hls_aph_per']) / 100.0
|
||||
elif match['hls_aph_flt']:
|
||||
alpha = float(match['hls_aph_flt'])
|
||||
|
||||
# Prepare the angle.
|
||||
if agl == 'grad':
|
||||
hue = hue * 400.0
|
||||
elif agl == 'rad':
|
||||
hue = hue / (2 * _math.pi)
|
||||
elif not agl or agl == 'deg':
|
||||
hue = hue / 360.0
|
||||
hue = hue % 1.0
|
||||
|
||||
if sat > 1 or lgt > 1:
|
||||
raise Exception
|
||||
|
||||
r, g, b = _hls_to_rgb(hue, lgt, sat)
|
||||
r, g, b = map(lambda x:int(round(x * 255)), (r, g, b))
|
||||
elif match['hwb_hue']:
|
||||
hue = float(match['hwb_hue'])
|
||||
agl = match['hwb_agl']
|
||||
|
||||
# Prepare the angle.
|
||||
if agl == 'grad':
|
||||
hue = hue * 400.0
|
||||
elif agl == 'rad':
|
||||
hue = hue / (2 * _math.pi)
|
||||
elif not agl or agl == 'deg':
|
||||
hue = hue / 360.0
|
||||
hue = hue % 1.0
|
||||
|
||||
# Saturation.
|
||||
if match['hwb_wht_per']:
|
||||
wht = float(match['hwb_wht_per']) / 100.0
|
||||
else:
|
||||
wht = float(match['hwb_wht_flt'])
|
||||
|
||||
# Light.
|
||||
if match['hwb_blk_per']:
|
||||
blk = float(match['hwb_blk_per']) / 100.0
|
||||
else:
|
||||
blk = float(match['hwb_blk_flt'])
|
||||
|
||||
if wht > 1 or blk > 1:
|
||||
raise Exception
|
||||
r, g, b = _hwb_to_rgb(hue, wht, blk)
|
||||
r, g, b = map(lambda x: int(round(x * 255)), (r, g, b))
|
||||
|
||||
if r < 0 or r > 255 or g < 0 or g > 255 or b < 0 or b > 255:
|
||||
raise Exception
|
||||
if alpha < 0.0 or alpha > 1.0:
|
||||
raise Exception
|
||||
alpha = round(alpha, 4)
|
||||
|
||||
return (r, g, b, alpha)
|
||||
|
||||
# End of file.
|
|
@ -1,22 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Conversions between color systems. """
|
||||
|
||||
from colorsys import hls_to_rgb
|
||||
|
||||
__all__ = ["hls_to_rgb", "hwb_to_rgb"]
|
||||
|
||||
def hwb_to_rgb(hue, w, b):
|
||||
""" Convert HWB to RGB color.
|
||||
https://drafts.csswg.org/css-color/#hwb-to-rgb """
|
||||
|
||||
r, g, b = hls_to_rgb(hue, 0.5, 1.0)
|
||||
f = lambda x: x * (1 - w - b) + w
|
||||
r, g, b = f(r), f(g), f(b)
|
||||
|
||||
return r, g, b
|
||||
|
||||
# End of file.
|
|
@ -1,83 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Smiley conversion.
|
||||
Just convert them™.
|
||||
"""
|
||||
|
||||
import regex as _re
|
||||
from html import escape as _htmlescape
|
||||
|
||||
__all__ = ["htmlsmileys"]
|
||||
|
||||
# ---
|
||||
# List of them.
|
||||
# ---
|
||||
|
||||
_Smileys_prefix = "/images/smileys/"
|
||||
|
||||
_Smileys = {
|
||||
'>:)': 'twisted.gif',
|
||||
'>:(': 'evil.gif',
|
||||
':)': 'smile.gif',
|
||||
';)': 'wink.gif',
|
||||
':(': 'sad.gif',
|
||||
':D': 'grin.gif',
|
||||
':p': 'hehe.gif',
|
||||
'8-)': 'cool2.gif',
|
||||
':@': 'mad.gif',
|
||||
'0_0': 'eek.gif',
|
||||
':E': 'mrgreen.gif',
|
||||
':O': 'shocked.gif',
|
||||
':s': 'confused2.gif',
|
||||
'^^': 'eyebrows.gif',
|
||||
":'(": 'cry.gif',
|
||||
# ':-°': ('whistle.gif', 'height: 15px;'),
|
||||
|
||||
# Name-based smileys.
|
||||
|
||||
':lol:': 'lol.gif',
|
||||
':oops:': 'confused2.gif',
|
||||
':grr:': 'evil.gif',
|
||||
':sry:': 'redface.gif',
|
||||
':mmm:': 'rolleyes.gif',
|
||||
':waza:': 'waza.gif',
|
||||
# ':whistle:': ('whistle.gif', 'height: 15px;'),
|
||||
':here:': 'pointer.gif',
|
||||
':bow:': 'bow.gif',
|
||||
':cool:': 'cool.gif',
|
||||
':good:': 'welldone.gif',
|
||||
':love:': 'love.gif',
|
||||
':aie:': 'banghead2.gif',
|
||||
':cry:': 'cry.gif',
|
||||
':facepalm:': 'facepalm.gif',
|
||||
':argh:': 'insults.gif',
|
||||
':?:': 'what.gif',
|
||||
':!:': 'excl.gif',
|
||||
':arrow:': 'here.gif',
|
||||
':grin:': 'grin.gif',
|
||||
}
|
||||
_Smileys_html = {_htmlescape(a): _Smileys_prefix + b \
|
||||
for a, b in _Smileys.items()}
|
||||
|
||||
def _Smiley_sub_html(m):
|
||||
return m.group(1) + '<img src="' + _Smileys_html[m.group(2)] \
|
||||
+ '">' + m.group(3)
|
||||
|
||||
_Smiley_html_re = _re.compile('(^|\\s)(' + '|'.join(map(_re.escape,
|
||||
_Smileys_html.keys())) + ')(\\s|$)')
|
||||
|
||||
# ---
|
||||
# Functions.
|
||||
# ---
|
||||
|
||||
def htmlsmileys(text):
|
||||
""" HTML smileys """
|
||||
|
||||
text = _Smiley_html_re.sub(_Smiley_sub_html, text)
|
||||
text = _Smiley_html_re.sub(_Smiley_sub_html, text)
|
||||
return text
|
||||
|
||||
# End of file.
|
|
@ -1,73 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Tag helpers for the translate utilities.
|
||||
As we ought to be able to make separate tag modules, this module
|
||||
does not hardcode the imports and makes it possible to import any
|
||||
custom module to isolate some tags from the others and make the
|
||||
`textoutpc` a generic module for BBcode.
|
||||
"""
|
||||
|
||||
from inspect import ismodule as _ismod, isclass as _isclass
|
||||
from .base import TextoutTag as _TextoutTag, TextoutBlockTag, \
|
||||
TextoutInlineTag, TextoutParagraphTag
|
||||
|
||||
__all__ = ["TextoutParagraphTag", "TextoutBlockTag", "TextoutInlineTag",
|
||||
"get_tag"]
|
||||
|
||||
# ---
|
||||
# Gathering of the tags.
|
||||
# ---
|
||||
|
||||
def _extract_tags(module):
|
||||
""" Load all tags from a module. """
|
||||
|
||||
tags = []
|
||||
|
||||
# Obtain the list of properties from the module.
|
||||
|
||||
try:
|
||||
ds = module.__all__
|
||||
except:
|
||||
ds = dir(module)
|
||||
|
||||
# Get the submodules from the module (usually different files in the
|
||||
# tags module folder).
|
||||
|
||||
for submodule in (obj for name, obj in ((nm, getattr(module, nm)) \
|
||||
for nm in ds) if (name == '__init__' or name[0] != '_') \
|
||||
and _ismod(obj)):
|
||||
obtained = _extract_tags(submodule)
|
||||
tags += [tag for tag in obtained if not any(tag is x for x in tags)]
|
||||
del obtained
|
||||
|
||||
# Extract the tags from the current module.
|
||||
|
||||
for tag in (obj for name, obj in ((nm, getattr(module, nm)) for nm in ds) \
|
||||
if name[0] != '_' and _isclass(obj) and issubclass(obj, _TextoutTag)):
|
||||
tags.append(tag)
|
||||
|
||||
return tags
|
||||
|
||||
_tags = _extract_tags(__import__("builtin", \
|
||||
{'__name__': __name__ + '.__init__'}, level=1))
|
||||
_aliases = {alias: tag for alias, tag in \
|
||||
[(alias, tag) for tag in _tags for alias in tag.aliases]}
|
||||
|
||||
# ---
|
||||
# Function to get a tag.
|
||||
# ---
|
||||
|
||||
def get_tag(name, value, output_type = 'html', tweaks = {}):
|
||||
""" Find a tag using its name. """
|
||||
|
||||
try:
|
||||
als = _aliases[name]
|
||||
als = als(name, value, output_type, tweaks)
|
||||
return als
|
||||
except:
|
||||
return None
|
||||
|
||||
# End of file.
|
|
@ -1,146 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Base class for textout tags. For your class to be used as a textout tag,
|
||||
you have to make it inherit one of these (usually `TextoutBlockTag`
|
||||
or `TextoutInlineTag`). """
|
||||
|
||||
import inspect as _inspect
|
||||
from functools import partial as _p
|
||||
from inspect import getargspec as _getargspec
|
||||
|
||||
__all__ = ["TextoutTag", "TextoutBlockTag", "TextoutInlineTag",
|
||||
"TextoutParagraphTag"]
|
||||
|
||||
def _getargscount(func):
|
||||
try:
|
||||
return len(_inspect.getfullargspec(func).args)
|
||||
except:
|
||||
return len(_inspect.getargspec(func).args)
|
||||
|
||||
# ---
|
||||
# Main base tag class.
|
||||
# For more about defining a tag, see `/TAGS.md`.
|
||||
# ---
|
||||
|
||||
class TextoutTag:
|
||||
""" The textout tag base class.
|
||||
Is initialized with these values:
|
||||
|
||||
<name (only special)><content><name>
|
||||
[<name (with brackets)>]<content>[/<name>]
|
||||
[<name (with brackets)>=<value>]<content>[/<name>] """
|
||||
|
||||
aliases = ()
|
||||
|
||||
def __init__(self, name, value, ot, tweaks):
|
||||
""" Initialize the textout tag with the documented members. """
|
||||
|
||||
# Store internal data.
|
||||
|
||||
self.__output_type = ot
|
||||
self.__tweaks = tweaks
|
||||
|
||||
self.output_type = ot
|
||||
|
||||
# Call both prepare functions.
|
||||
|
||||
if hasattr(self, 'prepare'):
|
||||
try:
|
||||
assert _getargscount(self.prepare) == 4
|
||||
args = (name, value, ot)
|
||||
except:
|
||||
args = (name, value)
|
||||
self.prepare(*args)
|
||||
if hasattr(self, 'prepare_' + ot):
|
||||
prep = getattr(self, 'prepare_' + ot)
|
||||
try:
|
||||
assert len(_getargspec(prep).args) == 4
|
||||
args = (name, value, ot)
|
||||
except:
|
||||
args = (name, value)
|
||||
prep(*args)
|
||||
|
||||
# Prepare the preprocessing elements.
|
||||
if hasattr(self, 'preprocess'):
|
||||
if hasattr(self, 'preprocess_' + ot):
|
||||
self.__preprocess0 = self.preprocess
|
||||
self.preprocess = self.__preprocess_double
|
||||
elif hasattr(self, 'preprocess_' + ot):
|
||||
self.preprocess = getattr(self, 'preprocess_' + ot)
|
||||
|
||||
if hasattr(self, 'preprocess'):
|
||||
self.__preprocess2 = self.preprocess
|
||||
self.preprocess = self.__preprocess_and_prepare
|
||||
else:
|
||||
self.__after_preprocess()
|
||||
|
||||
if hasattr(self, 'default_' + ot):
|
||||
self.default = getattr(self, 'default_' + ot)
|
||||
|
||||
def __preprocess_double(self, content):
|
||||
""" Preprocess using the two methods. """
|
||||
|
||||
ct = self.__preprocess0(content)
|
||||
if ct != None: content = ct; del ct
|
||||
ct = self.__preprocess1(content)
|
||||
if ct != None: content = ct; del ct
|
||||
return content
|
||||
|
||||
def __preprocess_and_prepare(self, content):
|
||||
""" Preprocess and do the things after. """
|
||||
|
||||
ret = self.__preprocess2(content)
|
||||
self.__after_preprocess()
|
||||
return ret
|
||||
|
||||
def __out(self, name):
|
||||
""" Generic function to call two output functions of the same type. """
|
||||
|
||||
getattr(self, '__' + name)()
|
||||
getattr(self, name + '_' + self.__output_type)()
|
||||
|
||||
def __after_preprocess(self):
|
||||
ot = self.__output_type
|
||||
|
||||
for otype in ('begin', 'content', 'end'):
|
||||
if hasattr(self, otype):
|
||||
if hasattr(self, otype + '_' + ot):
|
||||
setattr(self, '__' + otype, getattr(self, otype))
|
||||
setattr(self, otype, _p(self.__out, otype))
|
||||
elif hasattr(self, otype + '_' + ot):
|
||||
setattr(self, otype, getattr(self, otype + '_' + ot))
|
||||
|
||||
def tweak(self, key, default = None):
|
||||
try:
|
||||
return self.__tweaks[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
# ---
|
||||
# Role-specific base tag classes.
|
||||
# ---
|
||||
|
||||
class TextoutBlockTag(TextoutTag):
|
||||
pass
|
||||
class TextoutInlineTag(TextoutTag):
|
||||
pass
|
||||
|
||||
# ---
|
||||
# Default tag: paragraph.
|
||||
# ---
|
||||
|
||||
class TextoutParagraphTag(TextoutBlockTag):
|
||||
""" Main tag for basic paragraphs. """
|
||||
|
||||
notempty = True
|
||||
|
||||
def begin_html(self):
|
||||
return '<p>'
|
||||
|
||||
def end_html(self):
|
||||
return '</p>'
|
||||
|
||||
# End of file.
|
|
@ -1,86 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutInlineTag as _TextoutInlineTag
|
||||
from html import escape as _htmlescape
|
||||
|
||||
__all__ = ["TextoutLinkTag", "TextoutProfileTag"]
|
||||
|
||||
class TextoutLinkTag(_TextoutInlineTag):
|
||||
""" The main link tag.
|
||||
Example uses:
|
||||
|
||||
[url=https://example.org/hi]Go to example.org[/url]!
|
||||
[url=/Fr/index.php][/url]
|
||||
[url]https://random.org/randomize.php[/url]
|
||||
"""
|
||||
|
||||
aliases = ('[url]',)
|
||||
raw = True
|
||||
|
||||
def _validate(self):
|
||||
for prefix in ('http://', 'https://', 'ftp://', '/', '#'):
|
||||
if self._url.startswith(prefix):
|
||||
break
|
||||
else:
|
||||
raise Exception("No allowed prefix!")
|
||||
|
||||
def prepare(self, name, value):
|
||||
self._url = None
|
||||
|
||||
# If there is no value, wait until we have a content to
|
||||
# decide if we are valid or not.
|
||||
if value == None:
|
||||
self.preprocess = self._preprocess_if_no_value
|
||||
return
|
||||
|
||||
# Otherwise, get the URL and validate.
|
||||
self._url = value
|
||||
self._validate()
|
||||
self.default = self._default_if_value
|
||||
|
||||
def _default_if_value(self):
|
||||
return self._url
|
||||
|
||||
def _preprocess_if_no_value(self, content):
|
||||
self._url = content
|
||||
self._validate()
|
||||
|
||||
def begin_html(self):
|
||||
return '<a href="{}">'.format(_htmlescape(self._url))
|
||||
|
||||
def end_html(self):
|
||||
return '</a>'
|
||||
|
||||
def begin_lightscript(self):
|
||||
return '['
|
||||
|
||||
def end_lightscript(self):
|
||||
url = self._url.replace('(', '%28').replace(')', '%29')
|
||||
return ']({})'.format(url)
|
||||
|
||||
class TextoutProfileTag(TextoutLinkTag):
|
||||
""" A special link tag for Planète Casio's profiles.
|
||||
Adds the prefix to the content, and sets the value.
|
||||
Example uses:
|
||||
|
||||
[profil]Cakeisalie5[/profil]
|
||||
"""
|
||||
|
||||
aliases = ('[profil]', '[profile]')
|
||||
|
||||
def prepare(self, name, value):
|
||||
# Override the TextoutLinkTag's prepare method.
|
||||
pass
|
||||
|
||||
def preprocess(self, content):
|
||||
# FIXME: check the username content!
|
||||
username = content
|
||||
self._url = 'https://www.planet-casio.com/Fr/compte/voir_profil.php' \
|
||||
'?membre={}'.format(username)
|
||||
self._validate()
|
||||
|
||||
# End of file.
|
|
@ -1,109 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
|
||||
from ..base import TextoutBlockTag as _TextoutBlockTag
|
||||
import re as _re
|
||||
import urllib.parse as _urlparse
|
||||
from html import escape as _htmlescape
|
||||
|
||||
__all__ = ["TextoutVideoTag"]
|
||||
|
||||
_hexcode = _re.compile('[a-zA-Z0-9_]+')
|
||||
_numcode = _re.compile('^/[0-9]+$')
|
||||
_dailypath = _re.compile('^/video/([a-z0-9]+)$')
|
||||
|
||||
class TextoutVideoTag(_TextoutBlockTag):
|
||||
""" The video tag, puts a preview of the video whose URL is given.
|
||||
Only a few 'big' services are supported for now.
|
||||
Example uses:
|
||||
|
||||
[video]video_url[/video]
|
||||
[video tiny]video_url[/video tiny]
|
||||
[video]https://www.youtube.com/watch?v=yhXpV8hRKxQ[/video]
|
||||
"""
|
||||
|
||||
aliases = ('[video]', '[video tiny]')
|
||||
raw = True
|
||||
|
||||
def prepare(self, name, value):
|
||||
""" Prepare the video tag. """
|
||||
|
||||
self._sizeclass = "video-tiny" if "tiny" in name \
|
||||
else "video-medium"
|
||||
self._center = False
|
||||
|
||||
def _getvideo(self, url):
|
||||
""" Try to get the video type for preprocessing. """
|
||||
|
||||
url = _urlparse.urlparse(url)
|
||||
if not url.scheme in ('http', 'https'):
|
||||
raise Exception
|
||||
|
||||
if url.netloc == "youtu.be":
|
||||
self._id = url.path[1:]
|
||||
if not _hexcode.match(self._id):
|
||||
raise Exception
|
||||
self._type = "youtube"
|
||||
elif url.netloc in ('youtube.com', 'www.youtube.com'):
|
||||
if url.path != '/watch':
|
||||
raise Exception
|
||||
self._id = _urlparse.parse_qs(url.query)['v'][0]
|
||||
if not _hexcode.fullmatch(self._id):
|
||||
raise Exception
|
||||
self._type = "youtube"
|
||||
elif url.netloc in ('dailymotion.com', 'www.dailymotion.com'):
|
||||
self._code = _dailypath.match(url.path).groups()[0]
|
||||
self._type = "dailymotion"
|
||||
elif url.netloc in ('vimeo.com', 'www.vimeo.com'):
|
||||
self._code = url.path[1:]
|
||||
if not _numcode.match(self._code):
|
||||
raise Exception
|
||||
self._type = "vimeo"
|
||||
else:
|
||||
raise Exception
|
||||
|
||||
def preprocess(self, content):
|
||||
self._url = content
|
||||
|
||||
try:
|
||||
self._getvideo(content)
|
||||
except:
|
||||
url = _urlparse.urlparse(content)
|
||||
if not url.scheme in ('http', 'https'):
|
||||
raise Exception("No allowed prefix!")
|
||||
self._type = None
|
||||
|
||||
def content_html(self):
|
||||
""" Produce the embed code for the given type. """
|
||||
|
||||
if not self._type:
|
||||
url = _htmlescape(self._url)
|
||||
return '<a href="{}">{}</a>'.format(url, url)
|
||||
|
||||
code = '<div class="video-wrapper {}{}">'.format(self._sizeclass,
|
||||
" video-center" if self._center else "")
|
||||
|
||||
if self._type == "youtube":
|
||||
code += '<iframe ' \
|
||||
'src="https://www.youtube.com/embed/{}" frameborder="0" ' \
|
||||
'allowfullscreen></iframe>'.format(self._id)
|
||||
elif self._type == "dailymotion":
|
||||
code += '<iframe frameborder="0" ' \
|
||||
'src="https://www.dailymotion.com/embed/video/{}">' \
|
||||
'</iframe>'.format(self._code)
|
||||
elif self._type == "vimeo":
|
||||
code += '<iframe src="https://player.vimeo.com/video/{}' \
|
||||
'?title=0&byline=0&portrait=0" frameborder="0" ' \
|
||||
'webkitAllowFullScreen allowFullScreen>' \
|
||||
'</iframe>'.format(self._code)
|
||||
|
||||
return code + '</div>'
|
||||
|
||||
def content_lightscript(self):
|
||||
url = self._url.replace('[', '%5B').replace(']', '%5D')
|
||||
return '[[image:{}]]'.format(url)
|
||||
|
||||
# End of file.
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Built-in tags for the `textoutpc` module.
|
||||
Some of these tags will probably have to move to a separate module
|
||||
Planète Casio-specific, but still, here we are.
|
||||
"""
|
||||
|
||||
_names = ["Align", "Code", "Image", "Label", "Link", "Progress",
|
||||
"Quote", "Rot", "Show", "Spoiler", "Text", "Title", "Video"]
|
||||
for name in _names:
|
||||
__import__(name, globals(), level=1)
|
||||
|
||||
# End of file.
|
|
@ -1,60 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
#******************************************************************************
|
||||
# Copyright (C) 2018 Thomas "Cakeisalie5" Touhey <thomas@touhey.fr>
|
||||
# This file is part of the textoutpc project, which is MIT-licensed.
|
||||
#******************************************************************************
|
||||
""" Autolinking (URL extraction from raw text) in HTML. """
|
||||
|
||||
import regex as _re
|
||||
|
||||
__all__ = ["htmlurls", "lightscripturls"]
|
||||
|
||||
# ---
|
||||
# Autolinking regex.
|
||||
# ---
|
||||
|
||||
def _sub_html(m):
|
||||
sp = m.group('sp')
|
||||
url = m.group('url')
|
||||
aft = ''
|
||||
|
||||
# Hack for the last comma.
|
||||
if url[-1] == ',':
|
||||
url, aft = url[:-1], ','
|
||||
|
||||
text = '{}<a href="{}">{}</a>{}' \
|
||||
.format(sp, url, url, aft)
|
||||
return text
|
||||
|
||||
def _sub_lightscript(m):
|
||||
sp = m.group('sp')
|
||||
url = m.group('url')
|
||||
aft = ''
|
||||
|
||||
# Hack for the last comma.
|
||||
if url[-1] == ',':
|
||||
url, aft = url[:-1], ','
|
||||
|
||||
url = url.replace('<', '%3C')
|
||||
url = url.replace('>', '%3E')
|
||||
text = '{}<{}>{}'.format(sp, url, aft)
|
||||
return text
|
||||
|
||||
_reg = _re.compile("""\
|
||||
(?P<sp>^|\s|[[:punct:]])
|
||||
(?P<url>(https?|ftp):
|
||||
(?P<ucore>[^\[\]\(\)\s]* (\[(?&ucore)\]?)* (\((?&ucore)\)?)*)*
|
||||
)
|
||||
""", _re.VERBOSE | _re.M)
|
||||
|
||||
# ---
|
||||
# Main functions.
|
||||
# ---
|
||||
|
||||
def htmlurls(text):
|
||||
return _reg.sub(_sub_html, text)
|
||||
|
||||
def lightscripturls(text):
|
||||
return _reg.sub(_sub_lightscript, text)
|
||||
|
||||
# End of file.
|
Loading…
Reference in New Issue