Skip to content

Commit

Permalink
add command line interface
Browse files Browse the repository at this point in the history
  • Loading branch information
boidolr committed Jan 23, 2016
1 parent 39369d2 commit cc36d8b
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 2 deletions.
2 changes: 1 addition & 1 deletion continuous_integration/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ if [[ "$DISTRIB" == "conda" ]]; then

# Configure the conda environment and put it in the path using the
# provided versions
conda create -n testenv --yes python=$PYTHON_VERSION pip nose \
conda create -n testenv --yes python=$PYTHON_VERSION pip nose mock \
numpy=$NUMPY_VERSION
source activate testenv

Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
license='MIT',
ext_modules=[Extension("wordcloud.query_integral_image",
["wordcloud/query_integral_image.c"])],
scripts=['wordcloud/wordcloud_cli.py'],
packages=['wordcloud'],
package_data={'wordcloud': ['stopwords', 'DroidSansMono.ttf']}
)
109 changes: 109 additions & 0 deletions test/test_wordcloud_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import argparse
import inspect
import wordcloud_cli
import wordcloud as wc
import sys
from collections import namedtuple
from mock import patch, MagicMock
from nose.tools import assert_equal, assert_true, assert_in

from tempfile import NamedTemporaryFile

temp = NamedTemporaryFile()
ArgOption = namedtuple('ArgOption', ['cli_name', 'init_name', 'pass_value', 'fail_value'])
ARGUMENT_SPEC_TYPED = [
ArgOption(cli_name='width', init_name='width', pass_value=13, fail_value=1.),
ArgOption(cli_name='height', init_name='height', pass_value=15, fail_value=1.),
ArgOption(cli_name='margin', init_name='margin', pass_value=17, fail_value=1.),
ArgOption(cli_name='relative_scaling', init_name='relative_scaling', pass_value=1, fail_value='c')
]
ARGUMENT_SPEC_REMAINING = [
ArgOption(cli_name='stopwords', init_name='stopwords', pass_value=temp.name, fail_value=None),
ArgOption(cli_name='mask', init_name='mask', pass_value=temp.name, fail_value=None),
ArgOption(cli_name='fontfile', init_name='font_path', pass_value=temp.name, fail_value=None),
ArgOption(cli_name='color', init_name='color_func', pass_value='red', fail_value=None),
ArgOption(cli_name='background', init_name='background_color', pass_value='grey', fail_value=None)
]

def all_arguments():
arguments = []
arguments.extend(ARGUMENT_SPEC_TYPED)
arguments.extend(ARGUMENT_SPEC_REMAINING)
return arguments


def test_argument_spec_matches_to_constructor_args():
args = argparse.Namespace()
for option in all_arguments():
setattr(args, option.init_name, option.pass_value)

supported_args = inspect.getargspec(wc.WordCloud.__init__).args
supported_args.remove('self')
for arg_name in vars(args).keys():
assert_in(arg_name, supported_args)


def test_main_passes_arguments_through():
args = argparse.Namespace()
for option in all_arguments():
setattr(args, option.init_name, option.pass_value)
args.imagefile = NamedTemporaryFile()
args.text = 'some long text'

with patch('wordcloud_cli.wc.WordCloud', autospec=True) as mock_word_cloud:
instance = mock_word_cloud.return_value
instance.to_image.return_value = MagicMock()
wordcloud_cli.main(args)

posargs, kwargs = mock_word_cloud.call_args
for option in all_arguments():
assert_in(option.init_name, kwargs)


def check_argument(name, result_name, value):
text = NamedTemporaryFile()

args = wordcloud_cli.parse_args(['--text', text.name, '--' + name, str(value)])
assert_in(result_name, vars(args))


def check_argument_type(name, value):
text = NamedTemporaryFile()

try:
with patch('wordcloud_cli.sys.stderr') as mock_stdout:
args = wordcloud_cli.parse_args(['--text', text.name, '--' + name, str(value)])
raise AssertionError('argument "{}" was accepted even though the type did not match'.format(name))
except SystemExit:
pass
except ValueError:
pass


def test_parse_args_are_passed_along():
for option in all_arguments():
if option.cli_name != 'mask':
yield check_argument, option.cli_name, option.init_name, option.pass_value


def test_parse_arg_types():
for option in ARGUMENT_SPEC_TYPED:
yield check_argument_type, option.cli_name, option.fail_value


def test_check_duplicate_color_error():
color_mask_file = NamedTemporaryFile()
text_file = NamedTemporaryFile()

try:
wordcloud_cli.parse_args(['--color', 'red', '--colormask', color_mask_file.name, '--text', text_file.name])
raise AssertionError('parse_args(...) didn\'t raise')
except ValueError as e:
assert_true('specify either' in str(e), msg='expecting the correct error message, instead got: ' + str(e))


def test_parse_args_defaults_to_random_color():
text = NamedTemporaryFile()

args = wordcloud_cli.parse_args(['--text', text.name])
assert_equal(args.color_func, wc.random_color_func)
1 change: 0 additions & 1 deletion wordcloud/TODO
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
* command line interface
* html export
* good notebook interface
* by default differnt color schemes
Expand Down
83 changes: 83 additions & 0 deletions wordcloud/wordcloud_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
r"""Command-line tool to generate word clouds
Usage::
$ cat word.txt | wordcloud_cli.py
$ wordcloud_cli.py --text=words.txt --stopwords=stopwords.txt
"""
import argparse
import wordcloud as wc
import numpy as np
import sys
import io
from PIL import Image

def main(args):
wordcloud = wc.WordCloud(stopwords=args.stopwords, mask=args.mask,
width=args.width, height=args.height, font_path=args.font_path,
margin=args.margin, relative_scaling=args.relative_scaling,
color_func=args.color_func, background_color=args.background_color).generate(args.text)
image = wordcloud.to_image()

b = io.BytesIO()
image.save(b, format='png')
with args.imagefile:
args.imagefile.write(b.getvalue())

def parse_args(arguments):
prog = 'python wordcloud_cli.py'
description = ('A simple command line interface for wordcloud module.')
parser = argparse.ArgumentParser(description=description)
parser.add_argument('--text', metavar='file', type=argparse.FileType(), default='-',
help='specify file of words to build the word cloud (default: stdin)')
parser.add_argument('--stopwords', metavar='file', type=argparse.FileType(),
help='specify file of stopwords (containing one word per line) to remove from the given text after parsing')
parser.add_argument('--imagefile', metavar='file', type=argparse.FileType('w'), default='-',
help='file the completed PNG image should be written to (default: stdout)')
parser.add_argument('--fontfile', metavar='path', dest='font_path',
help='path to font file you wish to use (default: DroidSansMono)')
parser.add_argument('--mask', metavar='file', type=argparse.FileType(),
help='mask to use for the image form')
parser.add_argument('--colormask', metavar='file', type=argparse.FileType(),
help='color mask to use for image coloring')
parser.add_argument('--relative_scaling', type=float, default=0,
metavar='rs', help=' scaling of words by frequency (0 - 1)')
parser.add_argument('--margin', type=int, default=2,
metavar='width', help='spacing to leave around words')
parser.add_argument('--width', type=int, default=400,
metavar='width', help='define output image width')
parser.add_argument('--height', type=int, default=200,
metavar='height', help='define output image height')
parser.add_argument('--color', metavar='color',
help='use given color as coloring for the image - accepts any value from PIL.ImageColor.getcolor')
parser.add_argument('--background', metavar='color', default='black', type=str, dest='background_color',
help='use given color as background color for the image - accepts any value from PIL.ImageColor.getcolor')
args = parser.parse_args(arguments)

if args.colormask and args.color:
raise ValueError('specify either a color mask or a color function')

with args.text:
args.text = args.text.read()

if args.stopwords:
with args.stopwords:
args.stopwords = set(map(str.strip, args.stopwords.readlines()))

if args.mask:
args.mask = np.array(Image.open(args.mask))

color_func = wc.random_color_func
if args.colormask:
image = np.array(Image.open(args.colormask))
color_func = wc.ImageColorGenerator(image)

if args.color:
color_func = wc.get_single_color_func(args.color)

args.color_func = color_func
return args

if __name__ == '__main__':
main(parse_args(sys.argv[1:]))

0 comments on commit cc36d8b

Please sign in to comment.