├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── iconfonts.py ├── setup.cfg ├── setup.py └── tests └── unit-tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | MANIFEST 3 | tests/__pycache__/ 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Use it in any personal or commercial project you want. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.py *.in tests/*.py 2 | include LICENSE.txt 3 | include README.md 4 | prune dist 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-icons (`iconfonts.py`) 2 | 3 | Easily display icon fonts in python markdown. Just add the CSS necessary for your font and add this extension. 4 | 5 | This is a 3rd party extension for [Python Markdown](https://pythonhosted.org/Markdown/). You can see a [full list of 3rd party extensions here](https://github.com/waylan/Python-Markdown/wiki/Third-Party-Extensions). 6 | 7 | Although this works with any icon font, users can use a `mod` syntax to add more prefixed classes to support [Font Awesome](http://fortawesome.github.io/Font-Awesome/) and its special classes such as `2x, 3x, muted, spin, etc` 8 | 9 | Furthermore, users can add their own `user_mod` syntax to add additional, non-prefixed, pre-defined classes for greater control over their icons while allowing you to control exactly what styles are allowed. 10 | 11 | - You can create your own Icon Fonts using the IcoMoon app: http://icomoon.io/app/ 12 | - A great pre-made Icon Font is [Font Awesome (GitHub Project)](http://fortawesome.github.io/Font-Awesome/) 13 | 14 | See the [python markdown documentation](http://pythonhosted.org/Markdown/) for more information. 15 | 16 | Use it in any personal or commercial project you want. 17 | 18 | # Current Version: 2.1 19 | 20 | # Syntax: 21 | 22 | - Accepts a-z, A-Z, 0-9, _(underscore), and - (hypen) 23 | - Uses [HTML Entity](http://www.w3schools.com/html/html_entities.asp) like syntax: `&entity_name;` 24 | 25 | ``` 26 | &icon-html5; 27 | &icon-css3; 28 | &icon-my-icon; 29 | ``` 30 | 31 | Mod syntax: 32 | ``` 33 | &icon-html5:2x; 34 | &icon-quote:3x,muted; 35 | &icon-spinner:large,spin; 36 | ``` 37 | 38 | User mod syntax: 39 | ``` 40 | &icon-html5::red; 41 | &icon-quote:2x:bold; 42 | ``` 43 | 44 | #### Example Markdown: 45 | 46 | ``` 47 | I love &icon-html5; and &icon-css3; 48 | &icon-spinner:large,spin; Sorry we have to load... 49 | ``` 50 | 51 | ##### Output: 52 | 53 | ``` 54 | I love and 55 | Sorry we have to load... 56 | ``` 57 | 58 | # Installation: 59 | 60 | Just drop it in the extensions folder of the markdown package: `markdown/extensions` 61 | 62 | 63 | # Usage / Setup: 64 | 65 | #### Default Prefix is "icon-": 66 | 67 | ##### In a Django Template: 68 | `{{ textmd|markdown:"safe,iconfonts" }}` 69 | 70 | ##### In Python: 71 | ``` 72 | md = markdown.Markdown(extensions=['iconfonts']) 73 | converted_text = md.convert(text) 74 | ``` 75 | 76 | 77 | #### Use a custom Prefix: 78 | 79 | ##### In a Django Template: 80 | `{{ textmd|markdown:"safe,iconfonts(prefix=mypref-)" }}` 81 | 82 | ##### In Python: 83 | ``` 84 | md = markdown.Markdown(extensions=['iconfonts(prefix=mypref-)']) 85 | converted_text = md.convert(text) 86 | ``` 87 | 88 | #### No prefix (just in case you couldn't figure it out :P): 89 | This isn't suggested, as it will take over the already built in HTML Entities 90 | 91 | ##### In Python: 92 | ``` 93 | md = markdown.Markdown(extensions=['iconfonts(prefix=)']) 94 | converted_text = md.convert(text) 95 | ``` 96 | 97 | #### The `base` option allows for use of Bootstrap 3 and FontAwesome 4 icons 98 | 99 | ##### In Python: 100 | ``` 101 | md = markdown.Markdown(extensions=['iconfonts(base=icon)']) 102 | converted_text = md.convert(text) 103 | ``` 104 | 105 | **Input:** `&icon-html5;` 106 | 107 | **Output:** `` 108 | 109 | #### Combine options with a comma: 110 | ``` 111 | md = markdown.Markdown(extensions=['iconfonts(prefix=fa-, base=fa)']) 112 | ``` 113 | 114 | #### `prefix_base_pairs` option 115 | 116 | The `prefix_base_pairs` option allows for multiple prefix-base pairs to be specified, to allow you to support both Bootstrap 3/Glyphicon and FontAwesome icons 117 | 118 | ##### In Python: 119 | ``` 120 | md = markdown.Markdown(extensions=['iconfonts'], 121 | extension_configs={ 122 | 'iconfonts': { 123 | 'prefix_base_pairs': { 124 | 'fa-': 'fa', 125 | 'glyphicon-': 'glyphicon', 126 | } 127 | } 128 | }) 129 | converted_text = md.convert(text) 130 | ``` 131 | 132 | **Input:** `&glyphicon-remove; &fa-html5;` 133 | 134 | **Output:** `` 135 | 136 | # How to run the unit tests 137 | 138 | - Install `Markdown`: `pip install markdown` 139 | - Install markdown icons. Copy the `iconfonts.py` file into `site-packages/markdown/extensions/` 140 | - Navigate to the test directory in CMD/terminal and run `python unit-tests.py -v` 141 | -------------------------------------------------------------------------------- /iconfonts.py: -------------------------------------------------------------------------------- 1 | """ 2 | IconFonts Extension for Python-Markdown 3 | ======================================== 4 | 5 | Version: 2.1 6 | 7 | Description: 8 | Use this extension to display icon font icons in python markdown. Just add the css necessary for your font and add this extension. 9 | 10 | Features: 11 | - Support FontAwesome or Bootstrap 3/Glyphicons or both at the same time! 12 | - Allow users to specify additional modifiers, like 'fa-2x' from FontAwesome 13 | - Force users to use pre-defined classes to style their icons instead of 14 | allowing them to specify styles themselves 15 | - Allow users to specify additional classes, like 'red' 16 | 17 | Syntax: 18 | - Accepts a-z, A-Z, 0-9, _ (underscore), and - (hypen) 19 | - Uses HTML Entity like syntax 20 | 21 | &icon-html5; 22 | &icon-css3; 23 | &icon-my-icon; 24 | 25 | &icon-html5:2x; 26 | &icon-quote:3x,muted; 27 | &icon-spinner:large,spin; 28 | 29 | 30 | Example Markdown: 31 | I love &icon-html5; and &icon-css3; 32 | &icon-spinner:large,spin; Sorry we have to load... 33 | &icon-spinner:large,spin:red; Sorry we have to load... 34 | 35 | Output: 36 | I love and 37 | Sorry we have to load... 38 | Sorry we have to load... 39 | 40 | 41 | Installation: 42 | Just drop it in the extensions folder of the markdown package. (markdown/extensions). 43 | Also check out: https://pythonhosted.org/Markdown/extensions/index.html 44 | 45 | Usage/Setup: 46 | Default Prefix is "icon-": 47 | In a Django Template: 48 | {{ textmd|markdown:"safe,iconfonts" }} 49 | 50 | In Python: 51 | >>> text = '&icon-html5;' 52 | >>> md = markdown.Markdown(extensions=['iconfonts']) 53 | >>> converted_text = md.convert(text) 54 | '' 55 | 56 | 57 | Use a custom Prefix: 58 | In a Django Template: 59 | {{ textmd|markdown:"safe,iconfonts(prefix=mypref-)" }} 60 | 61 | In Python: 62 | >>> text = '&mypref-html5;' 63 | >>> md = markdown.Markdown(extensions=['iconfonts(prefix=mypref-)']) 64 | >>> converted_text = md.convert(text) 65 | '' 66 | 67 | 68 | Use no prefix (just in case you couldn't figure it out :P): 69 | In a Django Template: 70 | {{ textmd|markdown:"safe,iconfonts(prefix=)" }} 71 | 72 | In Python: 73 | >>> text = '&html5;' 74 | >>> md = markdown.Markdown(extensions=['iconfonts(prefix=)']) 75 | >>> converted_text = md.convert(text) 76 | '' 77 | 78 | Use the base option which allows for Bootstrap 3 and FontAwesome 4: 79 | In Python: 80 | >>> text = '&fa-html5;' 81 | >>> md = markdown.Markdown(extensions=['iconfonts(prefix=fa-, base=fa)']) 82 | >>> converted_text = md.convert(text) 83 | '' 84 | 85 | >>> text = '&glyphicon-remove;' 86 | >>> md = markdown.Markdown(extensions=['iconfonts(prefix=glyphicon-, base=glyphicon)']) 87 | >>> converted_text = md.convert(text) 88 | '' 89 | 90 | Or support both Bootstrap 3/Glyphicons and FontAwesome 4 at the same time: 91 | In Python: 92 | >>> text = '&fa-html5; &glyphicon-remove;'' 93 | >>> md = markdown.Markdown(extensions=['iconfonts'], 94 | >>> extension_configs={ 95 | >>> 'fa': 'fa', 96 | >>> 'glyphicon': 'glyphicon', 97 | >>> }) 98 | >>> converted_text = md.convert(text) 99 | '' 100 | '' 101 | 102 | 103 | Copyright 2014 [Eric Eastwood](http://ericeastwood.com/) 104 | 105 | Use it in any personal or commercial project you want. 106 | """ 107 | 108 | import markdown 109 | import re 110 | 111 | 112 | class IconFontsExtension(markdown.Extension): 113 | """ IconFonts Extension for Python-Markdown. """ 114 | 115 | def __init__(self, *args, **kwargs): 116 | 117 | # define default configs 118 | self.config = { 119 | 'prefix': ['icon-', "Custom class prefix."], 120 | 'base': ['', "Base class added to each icon"], 121 | 'prefix_base_pairs': [{}, "Prefix/base pairs"], 122 | } 123 | 124 | # Override defaults with user settings 125 | # This is legacy code for versions older than 2.5.1. 126 | if len(args): 127 | for key, value in args[0]: 128 | # convert strings to booleans 129 | if value == 'True': 130 | value = True 131 | if value == 'False': 132 | value = False 133 | if value == 'None': 134 | value = None 135 | 136 | self.setConfig(key, value) 137 | 138 | # Override defaults with user settings 139 | # This is not legacy code but is used instead of the super call below because we have to support the legacy version 140 | if hasattr(self, 'setConfigs'): 141 | self.setConfigs(kwargs) 142 | 143 | # We can use this instead of the legacy for loop to set the config above 144 | #super(IconFontsExtension, self).__init__(*args, **kwargs) 145 | 146 | def add_inline(self, md, name, klass, re, config): 147 | pattern = klass(re, md, config) 148 | md.inlinePatterns.add(name, pattern, "[a-zA-Z0-9-]+)(:(?P[a-zA-Z0-9-]+(,[a-zA-Z0-9-]+)*)?(:(?P[a-zA-Z0-9-]+(,[a-zA-Z0-9-]+)*)?)?)?;' 160 | # ^---------------------^^ ^ ^--------------^ ^ ^ ^ ^--------------^ ^ ^ ^ 161 | # | +-------------------------------------+ | +------------------------------------------+ | | 162 | # | +----------------------------------------------+ | 163 | # +------------------------------------------------------------------------------------------+ 164 | # This is the full regex we use. Only reason we have pieces above is to easily change the prefix to something custom 165 | icon_regex = ''.join([icon_regex_start, prefix, icon_regex_end]) 166 | 167 | # Register the global one 168 | self.add_inline(md, 'iconfonts', IconFontsPattern, icon_regex, config) 169 | 170 | # Register each of the pairings 171 | for _prefix, _base in config['prefix_base_pairs'].items(): 172 | 173 | _prefix_base = _prefix if _prefix[-1] != '-' else _prefix[:-1] 174 | 175 | icon_regex = ''.join([icon_regex_start, _prefix, icon_regex_end]) 176 | 177 | self.add_inline( 178 | md, 'iconfonts_{}'.format(_prefix_base), 179 | IconFontsPattern, icon_regex, 180 | {'prefix': _prefix, 'base': _base}) 181 | 182 | 183 | class IconFontsPattern(markdown.inlinepatterns.Pattern): 184 | def __init__(self, pattern, md, config): 185 | # Pass the patterna and markdown instance 186 | super(IconFontsPattern, self).__init__(pattern, md) 187 | 188 | self.config = config 189 | 190 | """ Return a element with the necessary classes""" 191 | def handleMatch(self, match): 192 | 193 | # The dictionary keys come from named capture groups in the regex 194 | match_dict = match.groupdict() 195 | 196 | # Create the element 197 | el = markdown.util.etree.Element("i") 198 | 199 | base = self.config['base'] 200 | prefix = self.config['prefix'] 201 | 202 | icon_class_name = match_dict.get("name") 203 | 204 | # Mods are modifier classes. The syntax in the markdown is: 205 | # "&icon-namehere:2x;" and with multiple "&icon-spinner:2x,spin;" 206 | mod_classes_string = "" 207 | if match_dict.get("mod"): 208 | # Make a string with each modifier like: "fa-2x fa-spin" 209 | mod_classes_string = ' '.join('{}{}'.format(prefix, c) for c in match_dict.get("mod").split(",") if c) 210 | 211 | # User mods are modifier classes that shouldn't be prefixed with 212 | # prefix. The syntax in the markdown is: 213 | # "&icon-namehere::red;" and with multiple "&icon-spinner::red,bold;" 214 | user_mod_classes_string = "" 215 | if match_dict.get("user_mod"): 216 | # Make a string with each modifier like "red bold" 217 | user_mod_classes_string = ' '.join(uc for uc in match_dict.get("user_mod").split(",") if uc) 218 | 219 | if prefix != '': 220 | icon_class = '{}{}'.format(prefix, icon_class_name) 221 | else: 222 | icon_class = icon_class_name 223 | 224 | # Add the icon classes to the element 225 | classes = '{} {} {} {}'.format(base, icon_class, mod_classes_string, user_mod_classes_string) 226 | 227 | # Clean up classes 228 | classes = classes.strip() 229 | classes = re.sub(r'\s{2,}', ' ', classes) 230 | 231 | el.set('class', classes) 232 | # This is for accessibility and text-to-speech browsers so they don't try to read it 233 | el.set('aria-hidden', 'true') 234 | return el 235 | 236 | 237 | # http://pythonhosted.org/Markdown/extensions/api.html#makeextension 238 | def makeExtension(*args, **kwargs): 239 | return IconFontsExtension(*args, **kwargs) 240 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | 3 | setup( 4 | name='markdown-icons', 5 | description='Easily display icon fonts in markdown.', 6 | py_modules=['iconfonts'], 7 | install_requires=['markdown', 'six'], 8 | author='Eric Eastwoord', 9 | author_email='contact@ericeastwood.com', 10 | url='https://github.com/MadLittleMods/markdown-icons', 11 | keywords='markdown, icons, fontawesome, bootstrap', 12 | classifiers=[ 13 | 'Development Status :: 3 - Alpha', 14 | 'Environment :: Web Environment', 15 | 'Intended Audience :: Developers', 16 | 'License :: Other/Proprietary License', 17 | 'Operating System :: OS Independent', 18 | 'Programming Language :: Python :: 2', 19 | 'Programming Language :: Python :: 3', 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /tests/unit-tests.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import os 4 | import markdown 5 | 6 | 7 | class TestMDI(unittest.TestCase): 8 | def test_vanilla(self): 9 | text = 'I love &icon-html5; and &icon-css3;.' 10 | expected = '

I love and .

' 11 | 12 | md = markdown.Markdown(extensions=['iconfonts']) 13 | converted_text = md.convert(text) 14 | 15 | self.assertEqual(converted_text, expected) 16 | 17 | def test_vanilla_mod(self): 18 | text = 'I also love &icon-spinner:spin; &icon-spinner:2x,spin;\n&icon-spinner:large,spin; Sorry we have to load...' 19 | expected = '

I also love \n Sorry we have to load...

' 20 | 21 | md = markdown.Markdown(extensions=['iconfonts']) 22 | converted_text = md.convert(text) 23 | 24 | self.assertEqual(converted_text, expected) 25 | 26 | def test_vanilla_user_mod(self): 27 | text = 'I also love &icon-spinner:spin:red; &icon-spinner:2x,spin:bold;\n&icon-spinner:large,spin; Sorry we have to load...' 28 | expected = '

I also love \n Sorry we have to load...

' 29 | 30 | md = markdown.Markdown(extensions=['iconfonts']) 31 | converted_text = md.convert(text) 32 | 33 | self.assertEqual(converted_text, expected) 34 | 35 | def test_prefix_base_pairs_setting(self): 36 | text = 'I also love &fa-spinner:2x,spin:red; &glyphicon-remove::bold;\n&fa-spinner:large,spin; Sorry we have to load...' 37 | expected = '

I also love \n Sorry we have to load...

' 38 | 39 | md = markdown.Markdown( 40 | extensions=['iconfonts'], 41 | extension_configs={ 42 | 'iconfonts': { 43 | 'prefix': 'icon-', 44 | 'base': 'icon', 45 | 'prefix_base_pairs': { 46 | 'fa-': 'fa', 47 | 'glyphicon-': 'glyphicon', 48 | } 49 | } 50 | }) 51 | converted_text = md.convert(text) 52 | 53 | self.assertEqual(converted_text, expected) 54 | 55 | def test_prefix_base_pairs_setting_with_normal_prefix_and_base_settings(self): 56 | text = 'I also love &fa-spinner:2x,spin:red; &glyphicon-remove::bold;\n&icon-spinner:large,spin; Sorry we have to load...' 57 | expected = '

I also love \n Sorry we have to load...

' 58 | 59 | md = markdown.Markdown( 60 | extensions=['iconfonts'], 61 | extension_configs={ 62 | 'iconfonts': { 63 | 'prefix': 'icon-', 64 | 'base': 'icon', 65 | 'prefix_base_pairs': { 66 | 'fa-': 'fa', 67 | 'glyphicon-': 'glyphicon', 68 | } 69 | } 70 | }) 71 | converted_text = md.convert(text) 72 | 73 | self.assertEqual(converted_text, expected) 74 | 75 | def test_custom_prefix(self): 76 | text = 'I love &mypref-html5; and &mypref-css3;.' 77 | expected = '

I love and .

' 78 | 79 | md = markdown.Markdown(extensions=['iconfonts(prefix=mypref-)']) 80 | converted_text = md.convert(text) 81 | 82 | self.assertEqual(converted_text, expected) 83 | 84 | def test_custom_prefix_mod(self): 85 | text = 'I also love &mypref-spinner:spin; &mypref-spinner:2x,spin;\n&mypref-spinner:large,spin; Sorry we have to load...' 86 | expected = '

I also love \n Sorry we have to load...

' 87 | 88 | md = markdown.Markdown(extensions=['iconfonts(prefix=mypref-)']) 89 | converted_text = md.convert(text) 90 | 91 | self.assertEqual(converted_text, expected) 92 | 93 | def test_base(self): 94 | text = 'I love &icon-html5; and &icon-css3;.' 95 | expected = '

I love and .

' 96 | 97 | md = markdown.Markdown(extensions=['iconfonts(base=icon)']) 98 | converted_text = md.convert(text) 99 | 100 | self.assertEqual(converted_text, expected) 101 | 102 | def test_complex(self): 103 | text = 'I love &fa-html5; and &fa-css3;.' 104 | expected = '

I love and .

' 105 | 106 | md = markdown.Markdown(extensions=['iconfonts(prefix=fa-, base=fa)']) 107 | converted_text = md.convert(text) 108 | 109 | self.assertEqual(converted_text, expected) 110 | 111 | 112 | """ 113 | # Save to file 114 | BASE_DIR = os.path.realpath(os.path.dirname(__file__)) 115 | with open(os.path.join(BASE_DIR, 'output.txt'), "w") as text_file: 116 | text_file.write(str(converted_text)) 117 | """ 118 | 119 | 120 | if __name__ == '__main__': 121 | unittest.main() 122 | --------------------------------------------------------------------------------