{ "info": { "author": "Aniket Bhattacharyea", "author_email": "i_abh_esc_wq@protonmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)" ], "description": "# Strana\nPython templating engine inspired by Django's templating system\n\nStrana is a templating engine with which it's easy to generate text files using templates.\nIt uses techniques inspired from Django's templating system with minor tweaks.\n\n## Installation\nDownload the release tarball from the 'releases' tab and run\n```\npip install strana\n``` \n## Examples\n```python\nfrom Strana.context import Context\nfrom Strana.template import Template\n\nsource = \"I'm such a {= quality =} boy\"\n\nt = Template(source, None, None)\nprint(t.render(Context(None, {'quality': 'good'}, 'root')))\n\n#Ouput: I'm such a good boy\n\n```\nAs you can see, the {= ... =} tells the parser that whatever is inside is actually a variable.\nAs soon as the parser encounters this block, it searches for the variable in the context.\nThe context is a special wrapper around a dictionary which provides access to variables.\nHere, the 'quality' variable is present inside context with a value of 'good'.\n\nLet's try another example\n```python\nsource = \"I'm such a {= quality.0 =} boy\"\n\nt = Template(source, None, None)\nprint(t.render(Context(None, {'quality': ['good','bad']}, 'root')))\n\n#Outputs: I'm such a good boy\n\n``` \nAs you can see, quality.0 is translated to quality[0].\nWhat about dictionaries?\n```python\nsource = \"I'm such a {= quality.aniket =} boy\"\n\nt = Template(source, None, None)\nprint(t.render(Context(None, {'quality': {'aniket':'good'}}, 'root')))\n\n#Output: I'm such a good boy\n```\nYou can have simple method calls too\n```python\nsource = \"I'm such a {= quality.get =} boy\"\nt = Template(source, None, None)\nprint(t.render(Context(None, {'quality': {'get':lambda :'good'}}, 'root')))\n\n#Output: I'm such a good boy\n```\n\nHowever you cannot have methods those take arguments.\n\nMoving on,\n```python\nsource = \"I'm such a {= quality =}{# This is a comment #} boy\"\n\nt = Template(source, None, None)\nprint(t.render(Context(None, {'quality': 'good'}, 'root')))\n\n#Output: I'm such a good boy\n```\n\n{# ... #} is a comment and is ignored (isn't it obvious?)\n\nAnd now the most important part, actions\n```python\nsource = \"\"\"\n {> do 5 times <}\n I'm such a {= quality =} boy\n {> /do <}\n\"\"\"\nt = Template(source, None, None)\nprint(t.render(Context(None, {'quality': 'good'}, 'root')))\n\n#Output\n\n\nI'm such a good boy\n\nI'm such a good boy\n\nI'm such a good boy\n\nI'm such a good boy\n\nI'm such a good boy\n\n\n\n\n```\nAction tags span over more than one lines, and can modify the output, as in the example, the \n\"do 5 times\" tag tells the parser to repeat its body 5 times. We'll see how to write our own action\n\nThese were basic examples. In order to move to advanced, we need to have some concepts\n\n## Concepts\n\n### Variables\nYou already know what they are. They're like normal python variables and optionally can be indexable.\n### Modifiers\nModifiers are optional \"functions\" that can access the value of a variable and modify it. They can optionally take arguments.\nSuppose we have a modifer called up, which can transform a variable to uppercase, and takes an argument which\nif true, will uppercase only the first letter, else will uppercase the whole value.\nThis is how we can use it - \n```python\nsource = \"I'm such a {= quality>>up=>False =} boy\"\n\n```\nThe '>>' tells the parser that whatever comes next is a modifier and the => tells that the next part is an argument\nWe'll see how to write our own modifiers.\n\n### Actions\nWe have already seen them in action (pun intended). They are basically functions which can optionally access the body, and optionally the context\nand cause the output to change. We'll see how to write our own actions soon.\n\n### Library\nThis class is responsible for acting as a collection of modifiers and actions. You can write your own modifiers and actions and register them\nwith a library and pass the library to the template.\n\n### Engine\nThey are optional helper classes which facilitate template loading and error printing.\nYou can specify a path to the engine, a string to be shown for errors, and a library. Next time, you can just pass in the name of template file to the engine,\nand it will load it with the proper library.\n\n### Node\nThink of nodes as boxes, where some data is stored. When parsing, each variable, comment and action\nis converted to a node. Each node has an id. When rendering, the render method of these nodes is called which returns the output for the corresponding\nnode. You can write your own node, although that's probably not necessary.\n\n###\nThis is a wrapper over a dict. Other than providing access to variables, it does some thing which we'll see soon.\n### Template\nThis is the heart of the system. This class is basically a collection of parsed nodes.\nYou pass the context and it gives the output. The same templates can be rendered with different contexts.\n\n## Advanced Usages\n### Using an engine\n\nIn most of the cases, using the DefaultEngine class is enough. This class provides you with\nprovided you with two basic builtin tags - the \"for i in x\" and \"do i times\" tag.\n**You should use this class only to provide the template path.** For your own libraries, pass them to the template.\n\n\n```python\nfrom Strana.engine import DefaultEngine\nfrom Strana.template import Template\nen = DefaultEngine('templates')\nsource = \"{> do 2 times <}HI{>/do}\"\n\n```\nNow use the **load_templates** method of the engine to load a template.\nThe template **should have an extension of '.ptm'**. If it's something else pass the whole name\n\n```python\nt=Template(en.load_template('xyz'),en,None) #Will load templates/xyz.ptm\nt=Template(en.load_template('xyz.abc'),en,None) #Will load templates/xyz.abc\n\n```\n### Using a library and writing own actions\n\nBefore starting writing an action, let's take a look at different types of actions\n\n* **Basic action:** They're simple, take no argument, are only one line, can save their output in the context, and do really simple jobs\n(e.g. Printing the time)\n* **Loop action:** These actions span over multiple lines, can access whatever is inside them, and require a closing tag.\n* **Pattern tag:** These tags can match custom patterns, say for example, \"Show me your marks\" or \"do n times\"\n\nNow let's see each of them in action.\n\nFirst import the library class and create an instance\n```python\nfrom Strana.library import Library\nr = Library()\n```\nNow to register a basic action, use the basic_action() function.\n\n**Every action must take node_id as first argument.**\n```python\n@r.basic_action(name='time',need_context=False)\n#name: defines the name of the block\n#need_context: If true, the context will be passed to the function and it must accept it as the second argument\ndef fun(node_id):# Every action must take the node ID as the first argument\n return str(datetime.now()) # Return a string.\n\nsource = \"{> time <} Test\"\nt = Template(source,None,[r]) # [r]= list of libraries, you can pass as many libraries as you want\nprint(t.render(Context(None,{},'root')))\n\n#Output: 2017-12-11 15:15:47.266049\n```\nYou can save the output using the 'as' keyword and use it later\n```python\nsource = \"{> time as t <} Hi {= t =}\"\n#The 'as' keyword stores the output in the variable t\n...\n\n#Output: Hi 2017-12-11 15:15:47.266049\n```\n\nFor registering a loop action, you'll use the loop_action method. \nThis method must be given a name, which will identify the name of the action\nFor example, a basic if action - \n\n```python\n@r.loop_action(name='if',need_context=True)\n#need_context: If True, the action will be passed the context\ndef fun(node_id,body,context,cond):\n#Loop actions will be passed the body and the 2nd function must be named body.\n#If need_context is True, the 3rd argument must be named context\n#Any additional arguments will came after.\n\n#body is just a list of nodes which can be rendered\n if cond:\n return ''.join([str(node.render(context)) for node in body])\n else:\n return ''\nsource = \"\"\"\n {> if True <}\n Hi\n {> /if <}\n\"\"\"\n#Output:\nHi\n```\n\nAs you can see, the arguments to the action are space separated. Moreover, an ending tag is required.\nThe ending tag is just / followed by the name.\n\nNow, time for a pattern action.\n\nTo register a pattern action, you'll use the pattern_action method. This method requires a name argument\njust like others, a need_context argument, and a need_body argument if you want access to the body.\nAdditionally, a pattern argument is required. The patterns can be any string. You'll use <> for arguments.\nDuring runtime, the pattern will be mathced, and whatever mathces in place of <> will be passed as strings.\n\nSay your pattern is \"do <> times\". Remember, the pattern must start with whatever was passed to the name argument.\n\nNow, you write {> do 8 times <}.\n\nWhen the parser encounters this line, it calls the action with \"8\" as argument. Note\nthat it's passed as a string. It's upto the function to change it to proper type.\n\nIf need_context is True, the context is passed and the 3rd argument must be named context.\n\nIf need_body is True, an ending tag is required, and the 2nd argument must be body.\n\nHere is the code of \"do n times\" pattern. It makes a variable named \"iteration\"\nwhich holds the value of current iteration and exists only in this block.\n```python\n@r.pattern_action(name='do', pattern='do <> times', need_body=True, need_context=True)\ndef do_action(node_id, body, context, times):\n#times is whatever matches with <>\n result = ''\n try:\n times = int(times)\n for i in range(times):\n c = context.push_temporary({'iteration': i}, node_id)\n result += ''.join([str(node.render(c)) for node in body])\n except ValueError:\n pass\n return result\n```\n\n### Writing modifiers\n\nTo write a modifier, we'll use the register_modifier method. This method also takes the name argument\nand a function. The first argument to this function must be the value which is to be modified.\nAny additional argument follows this.\n\nHere's the code of the up modifier\n```python\n@r.register_modifier(name='up')\ndef up(value, first_letter=False):\n return value.title() if first_letter else value.upper()\n```\nWhen you call it like this\n\n```python\n{= some_var>>up =}\n```\nThe value of some_var is passed as the first argument.\n\nTo supply the first_letter argument, you'll write this - \n```python\n{= some_var>>up=>True =}\n```\n\n### Builtin Library\n\nThe builtin library provides two useful action for you.\n\nOne is the \"do n times\" stated abov, and another is the \"for i in l\" action,\nwhich is exactly what you expect it to be.\n\n## Using Context\n\nContext class provides a helpful wrapper over a dict for managing variables. (I\nhave said that twice already). To initiate a context, you'll need to first import the class.\n```python\nfrom Strana.context import Context\n```\n\nThen pass an engine, a dict which holds the variables, and a node id.\nThis node id can be any string. Generally for the starting Context, we use 'root'.\n\n```python\nctx = Context(None,{'Hi':'Hello'},'root')\n```\n\nIf you print ctx, you'll see this - \n\n```python\nContext bound to node root: OrderedDict([('builtin', {'True': True, 'False': False, 'None': None}), ('root', {'Hi': 'Hello'})])\n```\n\nNote the 'builtin' part. Those three are provided for you. Isn't that cute?\n\nAlso, note that the context is bound to node 'root'. Whenever a node adds some new variables, it has to bind itself to the context.\nDon't worry, it happens automatically.\n\nThere are only two methods that you should use at this point - \n\n* push_temporary\n* push_permanent\n\npush_temporary returns a copy of current context with some new variables added. This is useful if you want some variables \nonly within a block.\n\nRemember the \"do n times\" action? Of course you don't. Here's the code.\n```python\n@r.pattern_action(name='do', pattern='do <> times', need_body=True, need_context=True)\ndef do_action(node_id, body, context, times):\n result = ''\n try:\n times = int(times)\n for i in range(times):\n #We push the new values temporarily\n #A new copy of the context is returned\n c = context.push_temporary({'iteration': i}, node_id)\n result += ''.join([str(node.render(c)) for node in body])\n except ValueError:\n pass\n return result\n```\nFor a closer look let's see this - \n```python\nctx = Context(None,{'Hi':'Hello'},'root')\nprint(ctx)\nwith ctx.push_temporary({'test':'Wow'},'some_node') as c:\n print(c)\n\n#Output\nContext bound to node root: OrderedDict([('builtin', {'True': True, 'False': False, 'None': None}), ('root', {'Hi': 'Hello'})])\nContext bound to node some_node: OrderedDict([('builtin', {'True': True, 'False': False, 'None': None}), ('root', {'Hi': 'Hello'}), ('some_node', {'test': 'Wow'})])\n```\n\npush_permanent pushes a dict which persists till the end.\n\nContext class also provides a pop_last method which pops the last node to which it was bound.\nIt should not be used by the user (and it has no practical use too at this point).\n\n## Author\n* **Aniket Bhattacharyea**\n## License\nThis project is licensed under GNU General Public License v3\n\n## Acknowledgments\n* [Django project](https://djangoproject.com)\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/i-abh-esc-wq/Strana", "keywords": "templating engine", "license": "GNU General Public License v3", "maintainer": "", "maintainer_email": "", "name": "Strana", "package_url": "https://pypi.org/project/Strana/", "platform": "", "project_url": "https://pypi.org/project/Strana/", "project_urls": { "Homepage": "https://github.com/i-abh-esc-wq/Strana" }, "release_url": "https://pypi.org/project/Strana/0.0.3/", "requires_dist": null, "requires_python": ">=3", "summary": "Strana is a templating engine inspired by Jinja2", "version": "0.0.3" }, "last_serial": 4994112, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "867ca44643a47d197c7794ba92542160", "sha256": "a6cbca7bfb0ab6cc2c68f3dc2ad9f4d5d13c9ff706cffc049c2b1ac6eb93de7f" }, "downloads": -1, "filename": "Strana-0.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "867ca44643a47d197c7794ba92542160", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3", "size": 22977, "upload_time": "2019-03-27T17:46:45", "url": "https://files.pythonhosted.org/packages/49/5f/46fac363aafd75344012e955141cc4a5721f1cad7342712967d2805fa22a/Strana-0.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "d3d9c8a113638e7281991f8595556e11", "sha256": "078a8f284313fd3ab8176671ae8acfea9bc0d69295d5149ed1161abfcd8c075a" }, "downloads": -1, "filename": "Strana-0.0.1.tar.gz", "has_sig": false, "md5_digest": "d3d9c8a113638e7281991f8595556e11", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 24858, "upload_time": "2019-03-27T17:46:48", "url": "https://files.pythonhosted.org/packages/50/70/63bac6216de154257f0918f36a2f59ec4f0e74073a2354921fe18ad507fb/Strana-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "22bc94f2769d2389e30fe0f00d729171", "sha256": "522ead641110eb0b3b976b257b6d777e2e35aac2ec046281dfce2487d85de1bf" }, "downloads": -1, "filename": "Strana-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "22bc94f2769d2389e30fe0f00d729171", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3", "size": 27430, "upload_time": "2019-03-27T17:55:46", "url": "https://files.pythonhosted.org/packages/d0/8f/420e4e4f4b8ba57672dac7248a6b2e6f02602ec427d66cebd06742687f86/Strana-0.0.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "6e031136da57ce07916911bdb78a488c", "sha256": "370897704803d45513de1fe877f87f58b69c622b878d0ccf475217542d6b56c4" }, "downloads": -1, "filename": "Strana-0.0.2.tar.gz", "has_sig": false, "md5_digest": "6e031136da57ce07916911bdb78a488c", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 30391, "upload_time": "2019-03-27T17:55:48", "url": "https://files.pythonhosted.org/packages/14/0d/38e4b524ced9605fd56292d6ec13421d58689da20f0a874c044eb59563cf/Strana-0.0.2.tar.gz" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "ed03f2a9f369c031b75bbf42b2052e95", "sha256": "db1b37695c23bb336a2525b847a8478a55b5648a03eb1c6e9d221c0527fb3423" }, "downloads": -1, "filename": "Strana-0.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "ed03f2a9f369c031b75bbf42b2052e95", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3", "size": 27422, "upload_time": "2019-03-27T17:56:54", "url": "https://files.pythonhosted.org/packages/e0/d9/4b1833ac7f8c0554d77046cea21ef3d88f4f7fbdcdfbeb580e45d6d0f41d/Strana-0.0.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2ac550ffbc1d431403683785009e4571", "sha256": "25e7746219a61a981a8df6681ef3d94a355fb65de592443620b3a5c942bf15e9" }, "downloads": -1, "filename": "Strana-0.0.3.tar.gz", "has_sig": false, "md5_digest": "2ac550ffbc1d431403683785009e4571", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 30378, "upload_time": "2019-03-27T17:56:56", "url": "https://files.pythonhosted.org/packages/14/e3/a8fd2838d71093e5c33229fee6b4d6fea2f767091990cf65a7cc7a695b61/Strana-0.0.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "ed03f2a9f369c031b75bbf42b2052e95", "sha256": "db1b37695c23bb336a2525b847a8478a55b5648a03eb1c6e9d221c0527fb3423" }, "downloads": -1, "filename": "Strana-0.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "ed03f2a9f369c031b75bbf42b2052e95", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3", "size": 27422, "upload_time": "2019-03-27T17:56:54", "url": "https://files.pythonhosted.org/packages/e0/d9/4b1833ac7f8c0554d77046cea21ef3d88f4f7fbdcdfbeb580e45d6d0f41d/Strana-0.0.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "2ac550ffbc1d431403683785009e4571", "sha256": "25e7746219a61a981a8df6681ef3d94a355fb65de592443620b3a5c942bf15e9" }, "downloads": -1, "filename": "Strana-0.0.3.tar.gz", "has_sig": false, "md5_digest": "2ac550ffbc1d431403683785009e4571", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3", "size": 30378, "upload_time": "2019-03-27T17:56:56", "url": "https://files.pythonhosted.org/packages/14/e3/a8fd2838d71093e5c33229fee6b4d6fea2f767091990cf65a7cc7a695b61/Strana-0.0.3.tar.gz" } ] }