From e1fef013836372cc60ace4b7573fda3c20cc88d7 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Fri, 28 Oct 2016 17:22:55 -0400 Subject: [PATCH 01/13] Modify first h1/p just like npm does --- bin/our-marky-markdown.js | 21 +++++++++++++++++++++ gratipay/utils/markdown.py | 19 ++++++++++++++++--- tests/py/test_markdown.py | 9 +++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100755 bin/our-marky-markdown.js diff --git a/bin/our-marky-markdown.js b/bin/our-marky-markdown.js new file mode 100755 index 0000000000..28c90b7f7e --- /dev/null +++ b/bin/our-marky-markdown.js @@ -0,0 +1,21 @@ +#!/usr/bin/env node +// Extend marky-markdown.js to support the package argument: +// https://www.npmjs.com/package/marky-markdown#npm-packages + +var fs = require('fs') +var path = require('path') +var marky = require('marky-markdown') + +if (process.argv.length < 3) { + console.log('Usage:\n\nour-marky-markdown some.md pkg > some.html') + process.exit() +} + +var filePath = path.resolve(process.cwd(), process.argv[2]) + +fs.readFile(filePath, function (err, data) { + if (err) throw err; + var package = process.argv[3] ? JSON.parse(process.argv[3]) : null; + var $ = marky(data.toString(), {package: package}) + process.stdout.write($.html()) +}) diff --git a/gratipay/utils/markdown.py b/gratipay/utils/markdown.py index 3c581854c1..12f9a7ab1f 100644 --- a/gratipay/utils/markdown.py +++ b/gratipay/utils/markdown.py @@ -1,7 +1,8 @@ from subprocess import Popen, PIPE -from markupsafe import Markup +import json import misaka as m # http://misaka.61924.nl/ +from markupsafe import Markup def render(markdown): @@ -12,10 +13,22 @@ def render(markdown): )) -def marky(markdown): +def marky(markdown, package=None): """Process markdown the same way npm does. + + Package should be a dict representing the package. If it includes `name` + and `description` then the first h1 and paragraph will have a + 'package-{name,description}-redundant' class added to them if they're + similar enough. If it includes `repository.url` then links will be changed + somehow. For details consult the docs and code: + + https://github.com/npm/marky-markdown + """ if type(markdown) is unicode: markdown = markdown.encode('utf8') - marky = Popen(("marky-markdown", "/dev/stdin"), stdin=PIPE, stdout=PIPE) + cmd = ("bin/our-marky-markdown.js", "/dev/stdin") + if package: + cmd += (json.dumps(package),) + marky = Popen(cmd, stdin=PIPE, stdout=PIPE) return Markup(marky.communicate(markdown)[0]) diff --git a/tests/py/test_markdown.py b/tests/py/test_markdown.py index 7b140148cb..b6871438bd 100644 --- a/tests/py/test_markdown.py +++ b/tests/py/test_markdown.py @@ -10,3 +10,12 @@ def test_marky_works(self): actual = HTMLParser().unescape(markdown.marky(md)).strip() expected = '

Hello World!

' assert actual == expected + + def test_marky_handles_npm_package(self): + md = "# Greetings, program!\nGreetings. Program." + pkg = {'name': 'greetings-program', 'description': 'Greetings, program.'} + actual = HTMLParser().unescape(markdown.marky(md, pkg)).strip() + expected = '''\ +

Greetings, program!

+

Greetings. Program.

''' + assert actual == expected From a332fb69826e93643055174b5da7fb9ef9d0b968 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 13:58:58 -0800 Subject: [PATCH 02/13] Keep up with upstream (not sure why) --- bin/our-marky-markdown.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/our-marky-markdown.js b/bin/our-marky-markdown.js index 28c90b7f7e..1035afae8c 100755 --- a/bin/our-marky-markdown.js +++ b/bin/our-marky-markdown.js @@ -14,8 +14,8 @@ if (process.argv.length < 3) { var filePath = path.resolve(process.cwd(), process.argv[2]) fs.readFile(filePath, function (err, data) { - if (err) throw err; + if (err) throw err var package = process.argv[3] ? JSON.parse(process.argv[3]) : null; - var $ = marky(data.toString(), {package: package}) - process.stdout.write($.html()) + var html = marky(data.toString(), {package: package}) + process.stdout.write(html) }) From 99b854401ead9a6d118358c8a5852790fa8ecfbe Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 14:13:12 -0800 Subject: [PATCH 03/13] Separate out fetching and processing of readmes --- gratipay/package_managers/readmes.py | 55 +++++++++++++++++++++++----- gratipay/package_managers/sync.py | 16 ++++++-- sql/branch.sql | 6 +++ tests/py/test_npm_sync.py | 38 +++++++++++++++++-- 4 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 sql/branch.sql diff --git a/gratipay/package_managers/readmes.py b/gratipay/package_managers/readmes.py index 17b43da52a..bd42e3b825 100644 --- a/gratipay/package_managers/readmes.py +++ b/gratipay/package_managers/readmes.py @@ -24,8 +24,8 @@ def http_fetch(package_name): return r.json() -def Syncer(db): - def sync(dirty, fetch=http_fetch): +def Fetcher(db): + def fetch(dirty, fetch=http_fetch): """Update all info for one package. """ log(dirty.name) @@ -43,23 +43,58 @@ def sync(dirty, fetch=http_fetch): db.run(''' UPDATE packages - SET readme=%s + SET readme_needs_to_be_processed=true , readme_raw=%s , readme_type=%s WHERE package_manager=%s AND name=%s - ''', ( markdown.marky(full['readme']) - , full['readme'] - , 'x-markdown/npm' + ''', ( full['readme'] + , 'x-markdown/marky' + , dirty.package_manager + , dirty.name + )) + + return fetch + + +def Processor(db): + def process(dirty): + """Processes the readme for a single page. + """ + log(dirty.name) + raw = db.one( 'SELECT readme_raw FROM packages ' + 'WHERE package_manager=%s and name=%s and readme_needs_to_be_processed' + , (dirty.package_manager, dirty.name) + ) + if raw is None: + return + processed = markdown.marky(raw) + db.run(''' + + UPDATE packages + SET readme=%s + , readme_needs_to_be_processed=false + WHERE package_manager=%s + AND name=%s + + ''', ( processed , dirty.package_manager , dirty.name )) - return sync + return process + + +def fetch(db): + dirty = db.all('SELECT package_manager, name ' + 'FROM packages WHERE readme_raw IS NULL ' + 'ORDER BY package_manager DESC, name DESC') + threaded_map(Fetcher(db), dirty, 4) -def sync_all(db): - dirty = db.all('SELECT package_manager, name FROM packages WHERE readme_raw IS NULL ' +def process(db): + dirty = db.all('SELECT id, package_manager, name, description, readme_raw ' + 'FROM packages WHERE readme_needs_to_be_processed' 'ORDER BY package_manager DESC, name DESC') - threaded_map(Syncer(db), dirty, 4) + threaded_map(Processor(db), dirty, 4) diff --git a/gratipay/package_managers/sync.py b/gratipay/package_managers/sync.py index f6a5a093b3..a5995c2a92 100644 --- a/gratipay/package_managers/sync.py +++ b/gratipay/package_managers/sync.py @@ -98,6 +98,8 @@ def log_stats(): def upsert(args): + """Take a CSV file from stdin and load it into Postgres. + """ from gratipay import wireup db = wireup.db(wireup.env()) fp = open(args.path) @@ -128,19 +130,25 @@ def upsert(args): """) -def readmes(args): +def fetch_readmes(args): + from gratipay import wireup + db = wireup.db(wireup.env()) + _readmes.fetch(db) + + +def process_readmes(args): from gratipay import wireup db = wireup.db(wireup.env()) - _readmes.sync_all(db) + _readmes.process(db) def parse_args(argv): p = argparse.ArgumentParser() - p.add_argument('command', choices=['serialize', 'upsert', 'readmes']) + p.add_argument('command', choices=['serialize', 'upsert', 'fetch-readmes', 'process-readmes']) p.add_argument('path', help='the path to the input file', nargs='?', default='/dev/stdin') return p.parse_args(argv) def main(argv=sys.argv): args = parse_args(argv[1:]) - globals()[args.command](args) + globals()[args.command.replace('-', '_')](args) diff --git a/sql/branch.sql b/sql/branch.sql new file mode 100644 index 0000000000..ef70461b86 --- /dev/null +++ b/sql/branch.sql @@ -0,0 +1,6 @@ +BEGIN; + ALTER TABLE packages ALTER COLUMN readme DROP NOT NULL; + ALTER TABLE packages ALTER COLUMN readme SET DEFAULT NULL; + UPDATE packages SET readme=NULL; + ALTER TABLE packages ADD COLUMN readme_needs_to_be_processed bool NOT NULL DEFAULT true; +END; diff --git a/tests/py/test_npm_sync.py b/tests/py/test_npm_sync.py index 4e0e4579bf..6144ec36c2 100644 --- a/tests/py/test_npm_sync.py +++ b/tests/py/test_npm_sync.py @@ -101,9 +101,9 @@ def test_sn_handles_empty_description_and_emails(self): assert package.emails == [] - # rs - readmes.Syncer + # rf - readmes.Fetcher - def test_rs_syncs_a_readme(self): + def test_rf_fetches_a_readme(self): self.db.run("INSERT INTO packages (package_manager, name, description, emails) " "VALUES ('npm', 'foo-package', 'A package', ARRAY[]::text[])") @@ -114,12 +114,42 @@ class DirtyPackage: def fetch(name): return {'name': 'foo-package', 'readme': '# Greetings, program!'} - readmes.Syncer(self.db)(DirtyPackage(), fetch=fetch) + readmes.Fetcher(self.db)(DirtyPackage(), fetch=fetch) + + package = self.db.one('SELECT * FROM packages') + assert package.name == 'foo-package' + assert package.description == 'A package' + assert package.readme == None + assert package.readme_needs_to_be_processed + assert package.readme_raw == '# Greetings, program!' + assert package.readme_type == 'x-markdown/marky' + assert package.emails == [] + + + # rp - readmes.Processor + + def test_rp_processes_a_readme(self): + self.db.run(''' + + INSERT + INTO packages (package_manager, name, description, readme_raw, readme_type, emails) + VALUES ('npm', 'foo-package', 'A package', '# Greetings, program!', 'x-markdown/marky', + ARRAY[]::text[]) + + ''') + + class DirtyPackage: + package_manager = 'npm' + name = 'foo-package' + + readmes.Processor(self.db)(DirtyPackage()) package = self.db.one('SELECT * FROM packages') assert package.name == 'foo-package' assert package.description == 'A package' assert package.readme == '

Greetings, program!

\n' + assert not package.readme_needs_to_be_processed assert package.readme_raw == '# Greetings, program!' - assert package.readme_type == 'x-markdown/npm' + assert package.readme_type == 'x-markdown/marky' assert package.emails == [] + From 9edf5b8518df5281a91fa3233a07d16b556bb70e Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 15:28:03 -0800 Subject: [PATCH 04/13] See if installing marky-markdown w/o -g helps --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b846203f7e..eccbc4e8ff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ before_install: # Sometimes ya just halfta ... - git clone https://github.com/lloyd/yajl.git && cd yajl && git checkout 2.1.0 && ./configure && sudo make install && cd .. - - npm install -g marky-markdown + - npm install marky-markdown cache: directories: - env/bin From 376de8cf09bee018f627b2ec2cb28c9fff319cd6 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 15:36:44 -0800 Subject: [PATCH 05/13] Ask pytest for verbose test failure info --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index eef1268f7f..fb634be968 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ pyflakes: env $(env_bin)/pyflakes *.py bin gratipay tests test: test-schema - $(py_test) --cov gratipay ./tests/ + $(py_test) -vv --cov gratipay ./tests/ @$(MAKE) --no-print-directory pyflakes pytest: env From 9f3e5be9cb27594681fb6a7d491bd529775353cc Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 16:09:02 -0800 Subject: [PATCH 06/13] Trim trailing newline --- tests/py/test_npm_sync.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/py/test_npm_sync.py b/tests/py/test_npm_sync.py index 6144ec36c2..17ab96e8f1 100644 --- a/tests/py/test_npm_sync.py +++ b/tests/py/test_npm_sync.py @@ -152,4 +152,3 @@ class DirtyPackage: assert package.readme_raw == '# Greetings, program!' assert package.readme_type == 'x-markdown/marky' assert package.emails == [] - From 9ec8a8bca49f7f68e7745df99fed2f875f825054 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 16:13:19 -0800 Subject: [PATCH 07/13] Bring other marky test up to date --- tests/py/test_markdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/py/test_markdown.py b/tests/py/test_markdown.py index b6871438bd..cac5e68f3f 100644 --- a/tests/py/test_markdown.py +++ b/tests/py/test_markdown.py @@ -16,6 +16,6 @@ def test_marky_handles_npm_package(self): pkg = {'name': 'greetings-program', 'description': 'Greetings, program.'} actual = HTMLParser().unescape(markdown.marky(md, pkg)).strip() expected = '''\ -

Greetings, program!

+

Greetings, program!

Greetings. Program.

''' assert actual == expected From 47daf8195368449ed34447589fc3e5c983f3a6e7 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 16:26:39 -0800 Subject: [PATCH 08/13] Refactor a bit - consolidate markdown tests in one place - rename render_like_npm for consistency - add some docstrings/futures/newlines --- gratipay/package_managers/readmes.py | 2 +- gratipay/utils/markdown.py | 10 ++++- tests/py/test_markdown.py | 62 ++++++++++++++++++++++++++-- tests/py/test_npm_sync.py | 2 +- tests/py/test_utils.py | 48 +-------------------- 5 files changed, 70 insertions(+), 54 deletions(-) diff --git a/gratipay/package_managers/readmes.py b/gratipay/package_managers/readmes.py index bd42e3b825..dc84ca649c 100644 --- a/gratipay/package_managers/readmes.py +++ b/gratipay/package_managers/readmes.py @@ -69,7 +69,7 @@ def process(dirty): ) if raw is None: return - processed = markdown.marky(raw) + processed = markdown.render_like_npm(raw) db.run(''' UPDATE packages diff --git a/gratipay/utils/markdown.py b/gratipay/utils/markdown.py index 12f9a7ab1f..71ed05ef51 100644 --- a/gratipay/utils/markdown.py +++ b/gratipay/utils/markdown.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + from subprocess import Popen, PIPE import json @@ -6,6 +8,12 @@ def render(markdown): + """Process markdown approximately the same way that GitHub used to. + + (Note that as of November, 2016 they are migrating to CommonMark, so we are + starting to drift.) + + """ return Markup(m.html( markdown, extensions=m.EXT_AUTOLINK | m.EXT_STRIKETHROUGH | m.EXT_NO_INTRA_EMPHASIS, @@ -13,7 +21,7 @@ def render(markdown): )) -def marky(markdown, package=None): +def render_like_npm(markdown, package=None): """Process markdown the same way npm does. Package should be a dict representing the package. If it includes `name` diff --git a/tests/py/test_markdown.py b/tests/py/test_markdown.py index cac5e68f3f..3aa4d22fbd 100644 --- a/tests/py/test_markdown.py +++ b/tests/py/test_markdown.py @@ -1,20 +1,74 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + from gratipay.testing import Harness from gratipay.utils import markdown from HTMLParser import HTMLParser + class TestMarkdown(Harness): - def test_marky_works(self): + # render + + def test_render_renders(self): + expected = "

Example

\n" + actual = markdown.render('Example') + assert expected == actual + + def test_render_escapes_scripts(self): + expected = '

Example alert “hi”;

\n' + actual = markdown.render('Example ') + assert expected == actual + + def test_render_renders_http_links(self): + expected = '

foo

\n' + assert markdown.render('[foo](http://example.com/)') == expected + expected = '

http://example.com/

\n' + assert markdown.render('') == expected + + def test_render_renders_https_links(self): + expected = '

foo

\n' + assert markdown.render('[foo](https://example.com/)') == expected + expected = '

https://example.com/

\n' + assert markdown.render('') == expected + + def test_render_escapes_javascript_links(self): + expected = '

[foo](javascript:foo)

\n' + assert markdown.render('[foo](javascript:foo)') == expected + expected = '

<javascript:foo>

\n' + assert markdown.render('') == expected + + def test_render_doesnt_allow_any_explicit_anchors(self): + expected = '

foo

\n' + assert markdown.render('foo') == expected + expected = '

foo

\n' + assert markdown.render('foo') == expected + expected = '

foo

\n' + assert markdown.render('foo') == expected + + def test_render_autolinks(self): + expected = '

http://google.com/

\n' + actual = markdown.render('http://google.com/') + assert expected == actual + + def test_render_no_intra_emphasis(self): + expected = '

Examples like this_one and this other_one.

\n' + actual = markdown.render('Examples like this_one and this other_one.') + assert expected == actual + + + # rln - render_like_npm + + def test_rln_works(self): md = "**Hello World!**" - actual = HTMLParser().unescape(markdown.marky(md)).strip() + actual = HTMLParser().unescape(markdown.render_like_npm(md)).strip() expected = '

Hello World!

' assert actual == expected - def test_marky_handles_npm_package(self): + def test_rln_handles_npm_package(self): md = "# Greetings, program!\nGreetings. Program." pkg = {'name': 'greetings-program', 'description': 'Greetings, program.'} - actual = HTMLParser().unescape(markdown.marky(md, pkg)).strip() + actual = HTMLParser().unescape(markdown.render_like_npm(md, pkg)).strip() expected = '''\

Greetings, program!

Greetings. Program.

''' diff --git a/tests/py/test_npm_sync.py b/tests/py/test_npm_sync.py index 17ab96e8f1..3ee2239551 100644 --- a/tests/py/test_npm_sync.py +++ b/tests/py/test_npm_sync.py @@ -27,7 +27,7 @@ def load(raw): missing_ijson = False try: - markdown.marky('test') + markdown.render_like_npm('test') except OSError: missing_marky_markdown = True else: diff --git a/tests/py/test_utils.py b/tests/py/test_utils.py index 726229ab2b..b94d803802 100644 --- a/tests/py/test_utils.py +++ b/tests/py/test_utils.py @@ -6,7 +6,7 @@ from aspen.http.response import Response from gratipay import utils from gratipay.testing import Harness, D -from gratipay.utils import i18n, markdown, pricing, encode_for_querystring, decode_from_querystring +from gratipay.utils import i18n, pricing, encode_for_querystring, decode_from_querystring from gratipay.utils.username import safely_reserve_a_username, FailedToReserveUsername, \ RanOutOfUsernameAttempts from psycopg2 import IntegrityError @@ -131,52 +131,6 @@ def reserve(cursor, username): with pytest.raises(RanOutOfUsernameAttempts): safely_reserve_a_username(cursor, gen_test_username, reserve) - def test_markdown_render_does_render(self): - expected = "

Example

\n" - actual = markdown.render('Example') - assert expected == actual - - def test_markdown_render_escapes_scripts(self): - expected = '

Example alert “hi”;

\n' - actual = markdown.render('Example ') - assert expected == actual - - def test_markdown_render_renders_http_links(self): - expected = '

foo

\n' - assert markdown.render('[foo](http://example.com/)') == expected - expected = '

http://example.com/

\n' - assert markdown.render('') == expected - - def test_markdown_render_renders_https_links(self): - expected = '

foo

\n' - assert markdown.render('[foo](https://example.com/)') == expected - expected = '

https://example.com/

\n' - assert markdown.render('') == expected - - def test_markdown_render_escapes_javascript_links(self): - expected = '

[foo](javascript:foo)

\n' - assert markdown.render('[foo](javascript:foo)') == expected - expected = '

<javascript:foo>

\n' - assert markdown.render('') == expected - - def test_markdown_render_doesnt_allow_any_explicit_anchors(self): - expected = '

foo

\n' - assert markdown.render('foo') == expected - expected = '

foo

\n' - assert markdown.render('foo') == expected - expected = '

foo

\n' - assert markdown.render('foo') == expected - - def test_markdown_render_autolinks(self): - expected = '

http://google.com/

\n' - actual = markdown.render('http://google.com/') - assert expected == actual - - def test_markdown_render_no_intra_emphasis(self): - expected = '

Examples like this_one and this other_one.

\n' - actual = markdown.render('Examples like this_one and this other_one.') - assert expected == actual - def test_srau_retries_work_with_db(self): self.make_participant('deadbeef') def gen_test_username(): From 0c632a6a7747f0967aa24a068dbafbd880c25e39 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 16:51:41 -0800 Subject: [PATCH 09/13] Conditionally fall back to pure-Python ijson At Travis and in production we still want to require YAJL, for performance, but we don't want to require that in local development. Note that we still skip the relevant tests if marky-markdown is not installed, so as to avoid introducing a Node dependency to local production. Unlike with ijson, which would silently degrade if local conditions were applied to production, a missing marky-markdown will hard-fail in production. --- .travis.yml | 1 + defaults.env | 5 ++++- gratipay/package_managers/sync.py | 34 ++++++++++++++++++------------ gratipay/wireup.py | 6 ++---- requirements.txt | 2 ++ tests/py/test_npm_sync.py | 13 +++--------- vendor/ijson-2.3.tar.gz | Bin 0 -> 10344 bytes 7 files changed, 32 insertions(+), 29 deletions(-) create mode 100644 vendor/ijson-2.3.tar.gz diff --git a/.travis.yml b/.travis.yml index eccbc4e8ff..ad4c559667 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,6 +23,7 @@ install: - env/bin/pip install --upgrade ijson==2.3.0 before_script: - echo "DATABASE_URL=dbname=gratipay" | tee -a tests/local.env local.env + - echo "REQUIRE_YAJL=true" | tee -a tests/local.env local.env - psql -U postgres -c 'CREATE DATABASE "gratipay";' - if [ "${TRAVIS_BRANCH}" = "master" -a "${TRAVIS_PULL_REQUEST}" = "false" ]; then rm -rfv tests/py/fixtures; fi script: LD_LIBRARY_PATH=/usr/local/lib make bgrun test doc diff --git a/defaults.env b/defaults.env index cd08ea16fa..fb30f11c43 100644 --- a/defaults.env +++ b/defaults.env @@ -87,6 +87,8 @@ ASPEN_PROJECT_ROOT=. ASPEN_SHOW_TRACEBACKS=yes ASPEN_WWW_ROOT=www/ +# This is used in our Procfile. (PORT is also used but is provided by +# Heroku; we don't set it ourselves in our app config.) # https://github.com/benoitc/gunicorn/issues/186 GUNICORN_OPTS="--workers=1 --timeout=99999999" @@ -96,5 +98,6 @@ TEAM_REVIEW_REPO=gratipay/test-gremlin TEAM_REVIEW_USERNAME= TEAM_REVIEW_TOKEN= -RESEND_VERIFICATION_THRESHOLD="3 minutes" +RESEND_VERIFICATION_THRESHOLD="3 minutes" # anything Postgres can interpret as an interval RAISE_SIGNIN_NOTIFICATIONS=no +REQUIRE_YAJL=false # speeds up npm syncing; should be true on production and Travis diff --git a/gratipay/package_managers/sync.py b/gratipay/package_managers/sync.py index a5995c2a92..78ea9532d9 100644 --- a/gratipay/package_managers/sync.py +++ b/gratipay/package_managers/sync.py @@ -8,6 +8,7 @@ import time import uuid +from gratipay import wireup from gratipay.package_managers import readmes as _readmes @@ -15,8 +16,13 @@ NULL = uuid.uuid4().hex -def import_ijson(): - import ijson.backends.yajl2_cffi as ijson +# helpers + +def import_ijson(env): + if env.require_yajl: + import ijson.backends.yajl2_cffi as ijson + else: + import ijson return ijson @@ -50,10 +56,12 @@ def serialize_one(out, package): return 1 -def serialize(args): +# cli subcommands + +def serialize(env, args, _): """Consume raw JSON from the npm registry and spit out CSV for Postgres. """ - ijson = import_ijson() + ijson = import_ijson(env) path = args.path parser = ijson.parse(open(path)) @@ -97,11 +105,9 @@ def log_stats(): log_stats() -def upsert(args): +def upsert(env, args, db): """Take a CSV file from stdin and load it into Postgres. """ - from gratipay import wireup - db = wireup.db(wireup.env()) fp = open(args.path) with db.get_cursor() as cursor: assert cursor.connection.encoding == 'UTF8' @@ -130,18 +136,16 @@ def upsert(args): """) -def fetch_readmes(args): - from gratipay import wireup - db = wireup.db(wireup.env()) +def fetch_readmes(env, args, db): _readmes.fetch(db) -def process_readmes(args): - from gratipay import wireup - db = wireup.db(wireup.env()) +def process_readmes(env, args, db): _readmes.process(db) +# cli plumbing + def parse_args(argv): p = argparse.ArgumentParser() p.add_argument('command', choices=['serialize', 'upsert', 'fetch-readmes', 'process-readmes']) @@ -150,5 +154,7 @@ def parse_args(argv): def main(argv=sys.argv): + env = wireup.env() args = parse_args(argv[1:]) - globals()[args.command.replace('-', '_')](args) + db = wireup.db(env) + globals()[args.command.replace('-', '_')](env, args, db) diff --git a/gratipay/wireup.py b/gratipay/wireup.py index 8aa1dd220e..9967e5da05 100644 --- a/gratipay/wireup.py +++ b/gratipay/wireup.py @@ -432,10 +432,8 @@ def env(): TEAM_REVIEW_USERNAME = unicode, TEAM_REVIEW_TOKEN = unicode, RAISE_SIGNIN_NOTIFICATIONS = is_yesish, - RESEND_VERIFICATION_THRESHOLD = unicode, # anything Postgres can interpret as an interval - - # This is used in our Procfile. (PORT is also used but is provided by - # Heroku; we don't set it ourselves in our app config.) + RESEND_VERIFICATION_THRESHOLD = unicode, + REQUIRE_YAJL = is_yesish, GUNICORN_OPTS = unicode, ) diff --git a/requirements.txt b/requirements.txt index 0e67de97d9..23b5640468 100644 --- a/requirements.txt +++ b/requirements.txt @@ -63,4 +63,6 @@ ./vendor/ipaddress-1.0.16.tar.gz ./vendor/cryptography-1.3.2.tar.gz +./vendor/ijson-2.3.tar.gz + -e . diff --git a/tests/py/test_npm_sync.py b/tests/py/test_npm_sync.py index 3ee2239551..8ffeb71272 100644 --- a/tests/py/test_npm_sync.py +++ b/tests/py/test_npm_sync.py @@ -7,7 +7,7 @@ import pytest from gratipay.utils import markdown from gratipay.testing import Harness -from gratipay.package_managers import readmes, sync +from gratipay.package_managers import readmes def load(raw): @@ -19,13 +19,6 @@ def load(raw): ).communicate(serialized)[0] -try: - sync.import_ijson() -except ImportError: - missing_ijson = True -else: - missing_ijson = False - try: markdown.render_like_npm('test') except OSError: @@ -33,8 +26,7 @@ def load(raw): else: missing_marky_markdown = False -@pytest.mark.skipif(missing_ijson, reason="missing ijson") -@pytest.mark.skipif(missing_marky_markdown, reason="missing marky-markdown") + class Tests(Harness): def test_packages_starts_empty(self): @@ -128,6 +120,7 @@ def fetch(name): # rp - readmes.Processor + @pytest.mark.skipif(missing_marky_markdown, reason="missing marky-markdown") def test_rp_processes_a_readme(self): self.db.run(''' diff --git a/vendor/ijson-2.3.tar.gz b/vendor/ijson-2.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e5dd11238d7a90daa019f98df00eea5f640e487a GIT binary patch literal 10344 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 Date: Tue, 8 Nov 2016 17:13:25 -0800 Subject: [PATCH 10/13] Allow skipping all marky-markdown-related tests With this we have the potential to miss a marky-markdown misconfiguration at Travis, but not in production. --- gratipay/testing/__init__.py | 23 +++++++++++++++++++++-- gratipay/utils/markdown.py | 8 ++++++-- tests/py/test_markdown.py | 4 +++- tests/py/test_npm_sync.py | 16 +++------------- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/gratipay/testing/__init__.py b/gratipay/testing/__init__.py index 349b302b64..190d55cb24 100644 --- a/gratipay/testing/__init__.py +++ b/gratipay/testing/__init__.py @@ -3,8 +3,13 @@ from __future__ import absolute_import, division, print_function, unicode_literals from decimal import Decimal -from gratipay.models.participant import Participant -from gratipay.models.team import Team + +import pytest +from aspen import log_dammit + +from ..models.participant import Participant +from ..models.team import Team +from ..utils import markdown D = Decimal #: P = Participant.from_username #: @@ -45,3 +50,17 @@ def debug_http(): requests_log = logging.getLogger("requests.packages.urllib3") requests_log.setLevel(logging.DEBUG) requests_log.propagate = True + + +# Provide a decorator to skip tests when marky-markdown is missing. + +try: + markdown.render_like_npm('test') +except OSError as exc: + MISSING_MARKY_MARKDOWN = True + log_dammit('Will skip marky-markdown-related tests because:', exc.args[0]) +else: + MISSING_MARKY_MARKDOWN = False + +def skipif_missing_marky_markdown(func): + return pytest.mark.skipif(MISSING_MARKY_MARKDOWN, reason="missing marky-markdown")(func) diff --git a/gratipay/utils/markdown.py b/gratipay/utils/markdown.py index 71ed05ef51..8120824e93 100644 --- a/gratipay/utils/markdown.py +++ b/gratipay/utils/markdown.py @@ -38,5 +38,9 @@ def render_like_npm(markdown, package=None): cmd = ("bin/our-marky-markdown.js", "/dev/stdin") if package: cmd += (json.dumps(package),) - marky = Popen(cmd, stdin=PIPE, stdout=PIPE) - return Markup(marky.communicate(markdown)[0]) + marky = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) + out, err = marky.communicate(markdown) + if marky.wait() > 0: + raise OSError(err) + return out + diff --git a/tests/py/test_markdown.py b/tests/py/test_markdown.py index 3aa4d22fbd..1c87fd933c 100644 --- a/tests/py/test_markdown.py +++ b/tests/py/test_markdown.py @@ -1,6 +1,6 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from gratipay.testing import Harness +from gratipay.testing import Harness, skipif_missing_marky_markdown from gratipay.utils import markdown from HTMLParser import HTMLParser @@ -59,12 +59,14 @@ def test_render_no_intra_emphasis(self): # rln - render_like_npm + @skipif_missing_marky_markdown def test_rln_works(self): md = "**Hello World!**" actual = HTMLParser().unescape(markdown.render_like_npm(md)).strip() expected = '

Hello World!

' assert actual == expected + @skipif_missing_marky_markdown def test_rln_handles_npm_package(self): md = "# Greetings, program!\nGreetings. Program." pkg = {'name': 'greetings-program', 'description': 'Greetings, program.'} diff --git a/tests/py/test_npm_sync.py b/tests/py/test_npm_sync.py index 8ffeb71272..5cd036f9d5 100644 --- a/tests/py/test_npm_sync.py +++ b/tests/py/test_npm_sync.py @@ -1,12 +1,10 @@ -"""Tests for syncing npm. Requires a `pip install ijson`, which requires yajl. Good luck! :^) +"""Tests for syncing npm. """ from __future__ import absolute_import, division, print_function, unicode_literals from subprocess import Popen, PIPE -import pytest -from gratipay.utils import markdown -from gratipay.testing import Harness +from gratipay.testing import Harness, skipif_missing_marky_markdown from gratipay.package_managers import readmes @@ -19,14 +17,6 @@ def load(raw): ).communicate(serialized)[0] -try: - markdown.render_like_npm('test') -except OSError: - missing_marky_markdown = True -else: - missing_marky_markdown = False - - class Tests(Harness): def test_packages_starts_empty(self): @@ -120,7 +110,7 @@ def fetch(name): # rp - readmes.Processor - @pytest.mark.skipif(missing_marky_markdown, reason="missing marky-markdown") + @skipif_missing_marky_markdown def test_rp_processes_a_readme(self): self.db.run(''' From 8b519c6804a8fe306b44fdd38886cb681b80ec5c Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 17:31:17 -0800 Subject: [PATCH 11/13] Fix syntax in defaults.env --- defaults.env | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/defaults.env b/defaults.env index fb30f11c43..e9eb67199f 100644 --- a/defaults.env +++ b/defaults.env @@ -98,6 +98,10 @@ TEAM_REVIEW_REPO=gratipay/test-gremlin TEAM_REVIEW_USERNAME= TEAM_REVIEW_TOKEN= -RESEND_VERIFICATION_THRESHOLD="3 minutes" # anything Postgres can interpret as an interval +# anything Postgres can interpret as an interval +RESEND_VERIFICATION_THRESHOLD="3 minutes" + RAISE_SIGNIN_NOTIFICATIONS=no -REQUIRE_YAJL=false # speeds up npm syncing; should be true on production and Travis + +# speeds up npm syncing; should be true on production and Travis +REQUIRE_YAJL=false From 3729efbbb30742adfcf80b5ec8820f31a83f3899 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 18:04:32 -0800 Subject: [PATCH 12/13] We don't need to modify the readme column anymore We're using readme_needs_to_be_updated instead. --- sql/branch.sql | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/sql/branch.sql b/sql/branch.sql index ef70461b86..a9e05ce01b 100644 --- a/sql/branch.sql +++ b/sql/branch.sql @@ -1,6 +1 @@ -BEGIN; - ALTER TABLE packages ALTER COLUMN readme DROP NOT NULL; - ALTER TABLE packages ALTER COLUMN readme SET DEFAULT NULL; - UPDATE packages SET readme=NULL; - ALTER TABLE packages ADD COLUMN readme_needs_to_be_processed bool NOT NULL DEFAULT true; -END; +ALTER TABLE packages ADD COLUMN readme_needs_to_be_processed bool NOT NULL DEFAULT true; From d0b5b4f1473e6313bb312cd4644d3f649b67a916 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Tue, 8 Nov 2016 18:21:04 -0800 Subject: [PATCH 13/13] Yay for tests! :) --- tests/py/test_npm_sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/py/test_npm_sync.py b/tests/py/test_npm_sync.py index 5cd036f9d5..ac5b40db48 100644 --- a/tests/py/test_npm_sync.py +++ b/tests/py/test_npm_sync.py @@ -101,7 +101,7 @@ def fetch(name): package = self.db.one('SELECT * FROM packages') assert package.name == 'foo-package' assert package.description == 'A package' - assert package.readme == None + assert package.readme == '' assert package.readme_needs_to_be_processed assert package.readme_raw == '# Greetings, program!' assert package.readme_type == 'x-markdown/marky'