Compare commits

...
13 Commits
Author SHA1 Message Date
mdipierro b02e8a6d5f fixed problem with custom import and upgraded jquery.js 2015-04-02 16:30:15 -05:00
mdipierro a2b17967cf Merge pull request #898 from ilvalle/issue-896
fix web2py/web2py#896, avoid to select blob fields in sqlform.grid
2015-04-02 16:24:26 -05:00
mdipierro 343ebf1714 Merge pull request #897 from BuhtigithuB/update-contrib/feedparser-py
Update gluon/contrib/feedparser.py from 5.1.2 -> 5.1.3
2015-04-02 16:23:34 -05:00
mdipierro e330791fe3 Merge pull request #894 from niphlod/fix/windows_build
fix windows build
2015-04-02 16:22:49 -05:00
mdipierro 5e1e97ffc4 Merge pull request #893 from niphlod/fix/duplicate_module
not needed anymore module
2015-04-02 16:20:39 -05:00
ilvalle 60f8816a10 fix web2py/web2py#896, avoid to select blob fields in sqlform.grid 2015-04-02 21:38:05 +02:00
Richard Vézina 6187c89ba6 Update gluon/contrib/feedparser.py from 5.1.2 -> 5.1.3 2015-04-02 13:58:48 -04:00
niphlod e441e15907 fix windows build 2015-04-01 21:35:52 +02:00
niphlod e6c3410639 not needed anymore module 2015-04-01 20:55:47 +02:00
mdipierro 6b6cb5839c Merge branch 'master' of github.com:web2py/web2py 2015-04-01 13:55:07 -05:00
mdipierro 2a7e9c0c59 possible fix for issue #892 2015-04-01 13:54:48 -05:00
mdipierro 5b637c0a82 Merge pull request #891 from leonelcamara/cache_cleanup
Fixes issue with locked files during cleanup
2015-04-01 12:41:52 -05:00
Leonel Câmara 5cdd7c1215 Fixes issue with locked files during cleanup 2015-04-01 18:27:05 +01:00
11 changed files with 77 additions and 304 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ update:
echo "remember that pymysql was tweaked"
src:
### Use semantic versioning
echo 'Version 2.10.1-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
echo 'Version 2.10.3-stable+timestamp.'`date +%Y.%m.%d.%H.%M.%S` > VERSION
### rm -f all junk files
make clean
### clean up baisc apps
+1 -1
View File
@@ -1 +1 @@
Version 2.10.2-stable+timestamp.2015.03.31.22.25.23
Version 2.10.3-stable+timestamp.2015.04.02.16.28.49
File diff suppressed because one or more lines are too long
+6 -6
View File
@@ -18,7 +18,7 @@
</ul>
<div class="tab-content">
<div class="tab-pane active" id="alltables">
<table>
<table class="table">
{{for db in sorted(databases):}}
{{for table in databases[db].tables:}}
{{qry='%s.%s.id>0'%(db,table)}}
@@ -40,7 +40,7 @@
{{=A("%s.%s" % (db,table),_href=URL('select',args=[db],vars=dict(query=qry)))}}
</th>
<td>
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn")}}
{{=A(str(T('New Record')),_href=URL('insert',args=[db,table]),_class="btn btn-default")}}
</td>
</tr>
{{pass}}
@@ -61,7 +61,7 @@
</pre>
{{pass}}
{{if table:}}
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn")}}<br/><br/>
{{=A(str(T('New Record')),_href=URL('insert',args=[request.args[0],table]),_class="btn btn-default")}}<br/><br/>
<h3>{{=T("Rows in Table")}}</h3><br/>
{{else:}}
<h3>{{=T("Rows selected")}}</h3><br/>
@@ -72,8 +72,8 @@
{{=T('"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN')}}</p>
<br/><br/>
<h4>{{=T("%s selected", nrows)}}</h4>
{{if start>0:}}{{=A(T('previous %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start-step)),_class="btn")}}{{pass}}
{{if stop<nrows:}}{{=A(T('next %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start+step)),_class="btn")}}{{pass}}
{{if start>0:}}{{=A(T('previous %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start-step)),_class="btn btn-default")}}{{pass}}
{{if stop<nrows:}}{{=A(T('next %s rows') % step,_href=URL('select',args=request.args[0],vars=dict(start=start+step)),_class="btn btn-default")}}{{pass}}
{{if rows:}}
<div style="overflow:auto; width:80%;">
{{linkto = lambda f, t, r: URL('update', args=[request.args[0], r, f]) if f else "#"}}
@@ -82,7 +82,7 @@
</div>
{{pass}}
<br/><br/><h3>{{=T("Import/Export")}}</h3><br/>
<a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}" class="btn">{{=T("export as csv file")}}</a>
<a href="{{=URL('csv',args=request.args[0],vars=dict(query=query))}}" class="btn btn-default">{{=T("export as csv file")}}</a>
{{=formcsv or ''}}
{{elif request.function=='insert':}}
+4 -3
View File
@@ -114,17 +114,18 @@ def app_cleanup(app, request):
for f in os.listdir(path):
try:
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
except IOError:
except (OSError, IOError):
r = False
# Remove cache files
CacheOnDisk(folder=apath('%s/cache/' % app, request)).clear()
path = apath('%s/cache/' % app, request)
CacheOnDisk(folder=path).clear()
if os.path.exists(path):
for f in os.listdir(path):
try:
if f[:1] != '.': recursive_unlink(os.path.join(path, f))
except IOError:
except (OSError, IOError):
r = False
return r
Executable → Regular
+48 -22
View File
@@ -9,7 +9,7 @@ Required: Python 2.4 or later
Recommended: iconv_codec <http://cjkpython.i18n.org/>
"""
__version__ = "5.1.2"
__version__ = "5.1.3"
__license__ = """
Copyright (c) 2010-2012 Kurt McKee <contactme@kurtmckee.org>
Copyright (c) 2002-2008 Mark Pilgrim
@@ -44,7 +44,8 @@ __contributors__ = ["Jason Diamond <http://injektilo.org/>",
"Sam Ruby <http://intertwingly.net/>",
"Ade Oshineye <http://blog.oshineye.com/>",
"Martin Pool <http://sourcefrog.net/>",
"Kurt McKee <http://kurtmckee.org/>"]
"Kurt McKee <http://kurtmckee.org/>",
"Bernd Schlapsi <https://github.com/brot>",]
# HTTP "User-Agent" header to send to servers when downloading feeds.
# If you are embedding feedparser in a larger application, you should
@@ -1971,6 +1972,7 @@ class _BaseHTMLProcessor(sgmllib.SGMLParser):
def handle_charref(self, ref):
# called for each character reference, e.g. for '&#160;', ref will be '160'
# Reconstruct the original character reference.
ref = ref.lower()
if ref.startswith('x'):
value = int(ref[1:], 16)
else:
@@ -2455,7 +2457,10 @@ class _MicroformatsParser:
linktype.startswith('video/') or \
(linktype.startswith('application/') and not linktype.endswith('xml')):
return 1
path = urlparse.urlparse(attrsD['href'])[2]
try:
path = urlparse.urlparse(attrsD['href'])[2]
except ValueError:
return 0
if path.find('.') == -1:
return 0
fileext = path.split('.').pop().lower()
@@ -2541,7 +2546,8 @@ class _RelativeURIResolver(_BaseHTMLProcessor):
('object', 'data'),
('object', 'usemap'),
('q', 'cite'),
('script', 'src')])
('script', 'src'),
('video', 'poster')])
def __init__(self, baseuri, encoding, _type):
_BaseHTMLProcessor.__init__(self, encoding, _type)
@@ -2618,13 +2624,13 @@ class _HTMLSanitizer(_BaseHTMLProcessor):
'loop', 'loopcount', 'loopend', 'loopstart', 'low', 'lowsrc', 'max',
'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'nohref',
'noshade', 'nowrap', 'open', 'optimum', 'pattern', 'ping', 'point-size',
'prompt', 'pqg', 'radiogroup', 'readonly', 'rel', 'repeat-max',
'repeat-min', 'replace', 'required', 'rev', 'rightspacing', 'rows',
'rowspan', 'rules', 'scope', 'selected', 'shape', 'size', 'span', 'src',
'start', 'step', 'summary', 'suppress', 'tabindex', 'target', 'template',
'title', 'toppadding', 'type', 'unselectable', 'usemap', 'urn', 'valign',
'value', 'variable', 'volume', 'vspace', 'vrml', 'width', 'wrap',
'xml:lang'])
'poster', 'pqg', 'preload', 'prompt', 'radiogroup', 'readonly', 'rel',
'repeat-max', 'repeat-min', 'replace', 'required', 'rev', 'rightspacing',
'rows', 'rowspan', 'rules', 'scope', 'selected', 'shape', 'size', 'span',
'src', 'start', 'step', 'summary', 'suppress', 'tabindex', 'target',
'template', 'title', 'toppadding', 'type', 'unselectable', 'usemap',
'urn', 'valign', 'value', 'variable', 'volume', 'vspace', 'vrml',
'width', 'wrap', 'xml:lang'])
unacceptable_elements_with_end_tag = set(['script', 'applet', 'style'])
@@ -2976,9 +2982,9 @@ def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, h
url_file_stream_or_string = 'http:' + url_file_stream_or_string[5:]
if not agent:
agent = USER_AGENT
# test for inline user:password for basic auth
# Test for inline user:password credentials for HTTP basic auth
auth = None
if base64:
if base64 and not url_file_stream_or_string.startswith('ftp:'):
urltype, rest = urllib.splittype(url_file_stream_or_string)
realhost, rest = urllib.splithost(rest)
if realhost:
@@ -3471,15 +3477,7 @@ _rfc822_match = re.compile(
"(?:%s, )?%s(?: %s)?" % (_rfc822_dayname, _rfc822_date, _rfc822_time)
).match
def _parse_date_rfc822(dt):
"""Parse RFC 822 dates and times, with one minor
difference: years may be 4DIGIT or 2DIGIT.
http://tools.ietf.org/html/rfc822#section-5"""
try:
m = _rfc822_match(dt.lower()).groupdict(0)
except AttributeError:
return None
def _parse_date_group_rfc822(m):
# Calculate a date and timestamp
for k in ('year', 'day', 'hour', 'minute', 'second'):
m[k] = int(m[k])
@@ -3512,8 +3510,36 @@ def _parse_date_rfc822(dt):
# Return the date and timestamp in UTC
return (stamp - delta).utctimetuple()
def _parse_date_rfc822(dt):
"""Parse RFC 822 dates and times, with one minor
difference: years may be 4DIGIT or 2DIGIT.
http://tools.ietf.org/html/rfc822#section-5"""
try:
m = _rfc822_match(dt.lower()).groupdict(0)
except AttributeError:
return None
return _parse_date_group_rfc822(m)
registerDateHandler(_parse_date_rfc822)
def _parse_date_rfc822_grubby(dt):
"""Parse date format similar to RFC 822, but
the comma after the dayname is optional and
month/day are inverted"""
_rfc822_date_grubby = "%s %s %s" % (_rfc822_month, _rfc822_day, _rfc822_year)
_rfc822_match_grubby = re.compile(
"(?:%s[,]? )?%s(?: %s)?" % (_rfc822_dayname, _rfc822_date_grubby, _rfc822_time)
).match
try:
m = _rfc822_match_grubby(dt.lower()).groupdict(0)
except AttributeError:
return None
return _parse_date_group_rfc822(m)
registerDateHandler(_parse_date_rfc822_grubby)
def _parse_date_asctime(dt):
"""Parse asctime-style dates"""
dayname, month, day, remainder = dt.split(None, 3)
-254
View File
@@ -1,254 +0,0 @@
# -*- encoding: utf-8 -*-
from imaplib import ParseFlags
# mockimaplib: A very simple mock server module for imap client APIs
# Copyright (C) 2014 Alan Etkin <spametki@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or(at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program. If not, see
# <http://www.gnu.org/licenses/lgpl.html>
"""
mockimaplib allows you to test applications connecting to a dummy imap
service. For more details on the api subset implemented,
refer to the imaplib docs.
The client should configure a dictionary to map imap string queries to sets
of entries stored in a message dummy storage dictionary. The module includes
a small set of default message records (SPAM and MESSAGES), two mailboxes
(Draft and INBOX) and a list of query/resultset entries (RESULTS).
Usage:
>>> import mockimaplib
>>> connection = mockimaplib.IMAP4_SSL(<host>)
>>> connection.login(<user>, <password>)
None
>>> connection.select("INBOX")
("OK", ... <mailbox length>)
# fetch commands specifying single uid or message id
# will try to get messages recorded in SPAM
>>> connection.uid(...)
<search query or fetch result>
# returns a string list of matching message ids
>>> connection.search(<query>)
("OK", ... "1 2 ... n")
"""
MESSAGES = (
'MIME-Version: 1.0\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:52:30 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:52:30 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <10101010101010010000010101010001010101001010010000001@mail.example.com>\r\nSubject: spam1\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse!\r\n\r\n\r\n',
'MIME-Version: 1.0\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:52:47 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:52:47 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <101010101010100100000101010100010101010010100100000010@mail.example.com>\r\nSubject: spam2\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse, nurse!',
'MIME-Version: 1.0\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:54:54 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:54:54 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <1010101010101001000001010101000101010100101001000000101@mail.example.com>\r\nSubject: spamalot1\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse!\r\n\r\n\r\n',
'MIME-Version: 1.0\r\n\r\nReceived: by 10.140.91.199 with HTTP; Mon, 27 Jan 2014 13:54:54 -0800 (PST)\r\nDate: Mon, 27 Jan 2014 19:54:54 -0200\r\nDelivered-To: nurse@example.com\r\nMessage-ID: <101010101010100100000101010100010101010010100100000010101@mail.example.com>\r\nSubject: spamalot2\r\nFrom: Mr. Gumby <gumby@example.com>\r\nTo: The nurse <nurse@example.com>\r\nContent-Type: text/plain; charset=ISO-8859-1\r\n\r\nNurse! ... Nurse! ... Nurse!\r\n\r\n\r\n')
SPAM = {
"INBOX": [
{"uid": "483209",
"headers": MESSAGES[0],
"complete": MESSAGES[0],
"flags": ""},
{"uid": "483211",
"headers": MESSAGES[1],
"complete": MESSAGES[1],
"flags": ""},
{"uid": "483225",
"headers": MESSAGES[2],
"complete": MESSAGES[2],
"flags": ""}],
"Draft":[
{"uid": "483432",
"headers": MESSAGES[3],
"complete": MESSAGES[3],
"flags": ""},]
}
RESULTS = {
# <query string>: [<str uid> | <long id>, ...]
"INBOX": {
"(ALL)": (1, 2, 3),
"(1:3)": (1, 2, 3)},
"Draft": {
"(1:1)": (1,)},
}
class Connection(object):
"""Dummy connection object for the imap client.
By default, uses the module SPAM and RESULT
sets (use Connection.setup for custom values)"""
def login(self, user, password):
pass
def __init__(self):
self._readonly = False
self._mailbox = None
self.setup()
def list(self):
return ('OK', ['(\\HasNoChildren) "/" "%s"' % key for key in self.spam])
def select(self, tablename, readonly=False):
self._readonly = readonly
"""args: mailbox, boolean
result[1][0] -> int last message id / mailbox lenght
result[0] = 'OK'
"""
self._mailbox = tablename
return ('OK', (len(SPAM[self._mailbox]), None))
def uid(self, command, uid, arg):
""" args:
command: "search" | "fetch"
uid: None | uid
parts: "(ALL)" | "(RFC822 FLAGS)" | "(RFC822.HEADER FLAGS)"
"search", None, "(ALL)" -> ("OK", ("uid_1 uid_2 ... uid_<mailbox length>", None))
"search", None, "<query>" -> ("OK", ("uid_1 uid_2 ... uid_n", None))
"fetch", uid, parts -> ("OK", (("<id> ...", "<raw message as specified in parts>"), "<flags>")
[0] [1][0][0] [1][0][1] [1][1]
"""
if command == "search":
return self._search(arg)
elif command == "fetch":
return self._fetch(uid, arg)
def _search(self, query):
return ("OK", (" ".join([str(item["uid"]) for item in self._get_messages(query)]), None))
def _fetch(self, value, arg):
try:
message = self.spam[self._mailbox][value - 1]
message_id = value
except TypeError:
for x, item in enumerate(self.spam[self._mailbox]):
if item["uid"] == value:
message = item
message_id = x + 1
break
parts = "headers"
if arg in ("(ALL)", "(RFC822 FLAGS)"):
parts = "complete"
return ("OK", (("%s " % message_id, message[parts]), message["flags"]))
def _get_messages(self, query):
if query.strip().isdigit():
return [self.spam[self._mailbox][int(query.strip()) - 1],]
elif query[1:-1].strip().isdigit():
return [self.spam[self._mailbox][int(query[1:-1].strip()) -1],]
elif query[1:-1].replace("UID", "").strip().isdigit():
for item in self.spam[self._mailbox]:
if item["uid"] == query[1:-1].replace("UID", "").strip():
return [item,]
messages = []
try:
for m in self.results[self._mailbox][query]:
try:
self.spam[self._mailbox][m - 1]["id"] = m
messages.append(self.spam[self._mailbox][m - 1])
except TypeError:
for x, item in enumerate(self.spam[self._mailbox]):
if item["uid"] == m:
item["id"] = x + 1
messages.append(item)
break
except IndexError:
# message removed
pass
return messages
except KeyError:
raise ValueError("The client issued an unexpected query: %s" % query)
def setup(self, spam={}, results={}):
"""adds custom message and query databases or sets
the values to the module defaults.
"""
self.spam = spam
self.results = results
if not spam:
for key in SPAM:
self.spam[key] = []
for d in SPAM[key]:
self.spam[key].append(d.copy())
if not results:
for key in RESULTS:
self.results[key] = RESULTS[key].copy()
def search(self, first, query):
""" args:
first: None
query: string with mailbox query (flags, date, uid, id, ...)
example: '2:15723 BEFORE 27-Jan-2014 FROM "gumby"'
result[1][0] -> "id_1 id_2 ... id_n"
"""
messages = self._get_messages(query)
ids = " ".join([str(item["id"]) for item in messages])
return ("OK", (ids, None))
def append(self, mailbox, flags, struct_time, message):
"""
result, data = self.connection.append(mailbox, flags, struct_time, message)
if result == "OK":
uid = int(re.findall("\d+", str(data))[-1])
"""
last = self.spam[mailbox][-1]
try:
uid = int(last["uid"]) +1
except ValueError:
alluids = []
for _mailbox in self.spam.keys():
for item in self.spam[_mailbox]:
try:
alluids.append(int(item["uid"]))
except:
pass
if len(alluids) > 0:
uid = max(alluids) + 1
else:
uid = 1
flags = "FLAGS " + flags
item = {"uid": str(uid), "headers": message, "complete": message, "flags": flags}
self.spam[mailbox].append(item)
return ("OK", "spam spam %s spam" % uid)
def store(self, *args):
"""
implements some flag commands
args: ("<id>", "<+|->FLAGS", "(\\Flag1 \\Flag2 ... \\Flagn)")
"""
message = self.spam[self._mailbox][int(args[0] - 1)]
old_flags = ParseFlags(message["flags"])
flags = ParseFlags("FLAGS" + args[2])
if args[1].strip().startswith("+"):
message["flags"] = "FLAGS (%s)" % " ".join(set(flags + old_flags))
elif args[1].strip().startswith("-"):
message["flags"] = "FLAGS (%s)" % " ".join([flag for flag in old_flags if not flag in flags])
def expunge(self):
"""implements removal of deleted flag messages"""
for x, item in enumerate(self.spam[self._mailbox]):
if "\\Deleted" in item["flags"]:
self.spam[self._mailbox].pop(x)
class IMAP4(object):
""">>> connection = IMAP4() # creates the dummy imap4 client object"""
def __new__(self, *args, **kwargs):
# args: (server, port)
return Connection()
IMAP4_SSL = IMAP4
+3
View File
@@ -47,6 +47,9 @@ def custom_importer(name, globals=None, locals=None, fromlist=None, level=-1):
If the import fails, it falls back on naive_importer
"""
if isinstance(name, unicode):
name = name.encode('utf8')
globals = globals or {}
locals = locals or {}
fromlist = fromlist or []
+1 -7
View File
@@ -23,6 +23,7 @@ base_modules = ['aifc', 'anydbm', 'array', 'asynchat', 'asyncore', 'atexit',
'collections', 'colorsys', 'compileall', 'compiler',
'compiler.ast', 'compiler.visitor', 'ConfigParser',
'contextlib', 'Cookie', 'cookielib', 'copy', 'copy_reg',
'collections',
'cPickle', 'cProfile', 'cStringIO', 'csv', 'ctypes',
'datetime', 'decimal', 'difflib', 'dircache', 'dis',
'doctest', 'DocXMLRPCServer', 'dumbdbm', 'dummy_thread',
@@ -67,13 +68,6 @@ base_modules = ['aifc', 'anydbm', 'array', 'asynchat', 'asyncore', 'atexit',
'MimeWriter', 'mimify', 'multifile', 'sets']
contributed_modules = []
for root, dirs, files in os.walk('gluon'):
for candidate in ['.'.join(
os.path.join(root, os.path.splitext(name)[0]).split(os.sep))
for name in files if name.endswith('.py')
and root.split(os.sep) != ['gluon', 'tests']
]:
contributed_modules.append(candidate)
# Python base version
python_version = sys.version[:3]
+1 -1
View File
@@ -2148,7 +2148,7 @@ class SQLFORM(FORM):
else:
fields = []
columns = []
filter1 = lambda f: isinstance(f, Field)
filter1 = lambda f: isinstance(f, Field) and f.type != 'blob'
filter2 = lambda f: isinstance(f, Field) and f.readable
for table in tables:
fields += filter(filter1, table)
+4 -1
View File
@@ -42,10 +42,13 @@ from gluon import *
from gluon.contrib.autolinks import expand_one
from gluon.contrib.markmin.markmin2html import \
replace_at_urls, replace_autolinks, replace_components
from pydal.objects import Table, Row, Set, Query
from pydal.objects import Row, Set, Query
import gluon.serializers as serializers
Table = DAL.Table
Field = DAL.Field
try:
# try stdlib (Python 2.6)
import json as json_parser