Explorar el Código

Added flickr plugin for liquid_tags

The flickr plugin uses the official flickr API to gather all
informations to generate the html tags. The user has to get a official
API key from flickr to use this.
Marvin Steadfast hace 8 años
padre
commit
3478c8c5d5

+ 10 - 0
liquid_tags/Readme.md

@@ -43,6 +43,16 @@ To insert a sized and labeled Instagram image in your document by its shortcode
 
 You can specify a size with `t`, `m`, or `l`.
 
+## Flickr Tag
+To insert a Flickr image to a post, follow these steps:
+
+1. Enable ``liquid_tags.flickr``
+2. [Get an API key from Flickr](https://www.flickr.com/services/apps/create/apply)
+3. Add FLICKR_API_KEY to your config
+4. Add this to your document:
+
+    {% flickr image_id [small|medium|large] ["alt text"|'alt text'] %}
+
 ## Youtube Tag
 To insert youtube video into a post, enable the
 ``liquid_tags.youtube`` plugin, and add to your document:

+ 116 - 0
liquid_tags/flickr.py

@@ -0,0 +1,116 @@
+"""
+Flickr Tag
+----------
+This implements a Liquid-style flickr tag for Pelican.
+
+IMPORTANT: You have to create a API key to access the flickr api.
+You can do this `here <https://www.flickr.com/services/apps/create/apply>`_.
+Add the created key to your config under FLICKR_API_KEY.
+
+Syntax
+------
+{% flickr image_id [small|medium|large] ["alt text"|'alt text'] %}
+
+Example
+--------
+{% flickr 18841055371 large "Fichte"}
+
+Output
+------
+<a href="https://www.flickr.com/photos/marvinxsteadfast/18841055371/"><img src="https://farm6.staticflickr.com/5552/18841055371_17ac287217_b.jpg" alt="Fichte"></a>
+"""
+import json
+import re
+try:
+    from urllib.request import urlopen
+    from urllib.parse import urlencode
+except ImportError:
+    from urllib import urlopen, urlencode
+from .mdx_liquid_tags import LiquidTags
+
+
+SYNTAX = '''{% flickr image_id [small|medium|large] ["alt text"|'alt text'] %}'''
+PARSE_SYNTAX = re.compile(('''(?P<photo_id>\S+)'''
+                           '''(?:\s+(?P<size>large|medium|small))?'''
+                           '''(?:\s+(['"]{0,1})(?P<alt>.+)(\\3))?'''))
+
+
+def get_info(photo_id, api_key):
+    ''' Get photo informations from flickr api. '''
+    query = urlencode({
+        'method': 'flickr.photos.getInfo',
+        'api_key': api_key,
+        'photo_id': photo_id,
+        'format': 'json',
+        'nojsoncallback': '1'
+    })
+
+    r = urlopen('https://api.flickr.com/services/rest/?' + query)
+    info = json.loads(r.read().decode('utf-8'))
+
+    if info['stat'] == 'fail':
+        raise ValueError(info['message'])
+
+    return info
+
+
+def source_url(farm, server, id, secret, size):
+    ''' Url for direct jpg use. '''
+    if size == 'small':
+        img_size = 'n'
+    elif size == 'medium':
+        img_size = 'c'
+    elif size == 'large':
+        img_size = 'b'
+
+    return 'https://farm{}.staticflickr.com/{}/{}_{}_{}.jpg'.format(
+        farm, server, id, secret, img_size)
+
+
+def generate_html(attrs, api_key):
+    ''' Returns html code. '''
+    # getting flickr api data
+    flickr_data = get_info(attrs['photo_id'], api_key)
+
+    # if size is not defined it will use large as image size
+    if 'size' not in attrs.keys():
+        attrs['size'] = 'large'
+
+    # if no alt is defined it will use the flickr image title
+    if 'alt' not in attrs.keys():
+        attrs['alt'] = flickr_data['photo']['title']['_content']
+
+    # return final html code
+    return '<a href="{}"><img src="{}" alt="{}"></a>'.format(
+        flickr_data['photo']['urls']['url'][0]['_content'],
+        source_url(flickr_data['photo']['farm'],
+                   flickr_data['photo']['server'],
+                   attrs['photo_id'],
+                   flickr_data['photo']['secret'],
+                   attrs['size']),
+        attrs['alt'])
+
+
+@LiquidTags.register('flickr')
+def flickr(preprocessor, tag, markup):
+    # getting flickr api key out of config
+    api_key = preprocessor.configs.getConfig('FLICKR_API_KEY')
+
+    # parse markup and extract data
+    attrs = None
+
+    match = PARSE_SYNTAX.search(markup)
+    if match:
+        attrs = dict(
+            [(key, value.strip())
+             for (key, value) in match.groupdict().items() if value])
+    else:
+        raise ValueError('Error processing input. '
+                         'Expected syntax: {}'.format(SYNTAX))
+
+    return generate_html(attrs, api_key)
+
+
+# ---------------------------------------------------
+# This import allows image tag to be a Pelican plugin
+from liquid_tags import register

+ 5 - 3
liquid_tags/mdx_liquid_tags.py

@@ -20,10 +20,12 @@ from functools import wraps
 LIQUID_TAG = re.compile(r'\{%.*?%\}', re.MULTILINE | re.DOTALL)
 EXTRACT_TAG = re.compile(r'(?:\s*)(\S+)(?:\s*)')
 LT_CONFIG = { 'CODE_DIR': 'code',
-              'NOTEBOOK_DIR': 'notebooks'
+              'NOTEBOOK_DIR': 'notebooks',
+              'FLICKR_API_KEY': 'flickr'
 }
-LT_HELP = { 'CODE_DIR' : 'Code directory for include_code subplugin', 
-            'NOTEBOOK_DIR' : 'Notebook directory for notebook subplugin'
+LT_HELP = { 'CODE_DIR' : 'Code directory for include_code subplugin',
+            'NOTEBOOK_DIR' : 'Notebook directory for notebook subplugin',
+            'FLICKR_API_KEY': 'Flickr key for accessing the API'
 }
 
 class _LiquidTagsPreprocessor(markdown.preprocessors.Preprocessor):

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 1 - 0
liquid_tags/test_data/flickr.json


+ 79 - 0
liquid_tags/test_flickr.py

@@ -0,0 +1,79 @@
+from . import flickr
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
+import os
+import pytest
+import re
+
+
+PLUGIN_DIR = os.path.dirname(__file__)
+TEST_DATA_DIR = os.path.join(PLUGIN_DIR, 'test_data')
+
+
+@pytest.mark.parametrize('input,expected', [
+    ('18873146680 large "test 1"',
+     dict(photo_id='18873146680',
+          size='large',
+          alt='test 1')),
+    ('18873146680 large \'test 1\'',
+     dict(photo_id='18873146680',
+          size='large',
+          alt='test 1')),
+    ('18873143536360 medium "test number two"',
+     dict(photo_id='18873143536360',
+          size='medium',
+          alt='test number two')),
+    ('18873143536360 small "test number 3"',
+     dict(photo_id='18873143536360',
+          size='small',
+          alt='test number 3')),
+    ('18873143536360 "test 4"',
+     dict(photo_id='18873143536360',
+          size=None,
+          alt='test 4')),
+    ('18873143536360',
+     dict(photo_id='18873143536360',
+          size=None,
+          alt=None)),
+    ('123456 small',
+     dict(photo_id='123456',
+          size='small',
+          alt=None))
+])
+def test_regex(input, expected):
+    assert re.match(flickr.PARSE_SYNTAX, input).groupdict() == expected
+
+
+@pytest.mark.parametrize('input,expected', [
+    (['1', 'server1', '1', 'secret1', 'small'],
+     'https://farm1.staticflickr.com/server1/1_secret1_n.jpg'),
+    (['2', 'server2', '2', 'secret2', 'medium'],
+     'https://farm2.staticflickr.com/server2/2_secret2_c.jpg'),
+    (['3', 'server3', '3', 'secret3', 'large'],
+     'https://farm3.staticflickr.com/server3/3_secret3_b.jpg')
+])
+def test_source_url(input, expected):
+    assert flickr.source_url(
+        input[0], input[1], input[2], input[3], input[4]) == expected
+
+
+@patch('liquid_tags.flickr.urlopen')
+def test_generage_html(mock_urlopen):
+    # mock the return to deliver the flickr.json file instead
+    with open(TEST_DATA_DIR + '/flickr.json', 'rb') as f:
+        mock_urlopen.return_value.read.return_value = f.read()
+
+        attrs = dict(
+            photo_id='1234567',
+            size='large',
+            alt='this is a test'
+        )
+
+        expected = ('<a href="https://www.flickr.com/photos/'
+                    'marvinxsteadfast/18841055371/">'
+                    '<img src="https://farm6.staticflickr.com/5552/1234567_'
+                    '17ac287217_b.jpg" alt="this is a test"></a>')
+
+        assert flickr.generate_html(attrs, 'abcdef') == expected

+ 1 - 0
liquid_tags/tox.ini

@@ -13,5 +13,6 @@ deps =
   	pytest-capturelog
 	pelican
 	markdown
+	mock
 	ipython2: ipython[notebook]>=2,<3
 	ipython3: ipython[notebook]