removed 1st edition code
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
Sample code for Chapter 14 - "Iterables, iterators and generators"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
||||
@@ -1,31 +0,0 @@
|
||||
===========================================
|
||||
Tests for arithmetic progression generators
|
||||
===========================================
|
||||
|
||||
Tests with built-in numeric types::
|
||||
|
||||
>>> ap = aritprog_gen(1, .5, 3)
|
||||
>>> list(ap)
|
||||
[1.0, 1.5, 2.0, 2.5]
|
||||
>>> ap = aritprog_gen(0, 1/3, 1)
|
||||
>>> list(ap)
|
||||
[0.0, 0.3333333333333333, 0.6666666666666666]
|
||||
|
||||
|
||||
Tests with standard library numeric types::
|
||||
|
||||
>>> from fractions import Fraction
|
||||
>>> ap = aritprog_gen(0, Fraction(1, 3), 1)
|
||||
>>> list(ap)
|
||||
[Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)]
|
||||
>>> from decimal import Decimal
|
||||
>>> ap = aritprog_gen(0, Decimal('.1'), .3)
|
||||
>>> list(ap)
|
||||
[Decimal('0'), Decimal('0.1'), Decimal('0.2')]
|
||||
|
||||
|
||||
Test producing an empty series::
|
||||
|
||||
>>> ap = aritprog_gen(0, 1, 0)
|
||||
>>> list(ap)
|
||||
[]
|
||||
@@ -1,26 +0,0 @@
|
||||
"""
|
||||
Demonstrate difference between Arithmetic Progression calculated
|
||||
as a series of increments accumulating errors versus one addition
|
||||
and one multiplication.
|
||||
"""
|
||||
|
||||
from fractions import Fraction
|
||||
from aritprog_v0 import ArithmeticProgression as APv0
|
||||
from aritprog_v1 import ArithmeticProgression as APv1
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
ap0 = iter(APv0(1, .1))
|
||||
ap1 = iter(APv1(1, .1))
|
||||
ap_frac = iter(APv1(Fraction(1, 1), Fraction(1, 10)))
|
||||
epsilon = 10**-10
|
||||
iteration = 0
|
||||
delta = next(ap0) - next(ap1)
|
||||
frac = next(ap_frac)
|
||||
while abs(delta) <= epsilon:
|
||||
delta = next(ap0) - next(ap1)
|
||||
frac = next(ap_frac)
|
||||
iteration +=1
|
||||
|
||||
print('iteration: {}\tfraction: {}\tepsilon: {}\tdelta: {}'.
|
||||
format(iteration, frac, epsilon, delta))
|
||||
@@ -1,37 +0,0 @@
|
||||
import doctest
|
||||
import importlib
|
||||
import glob
|
||||
|
||||
|
||||
TARGET_GLOB = 'aritprog*.py'
|
||||
TEST_FILE = 'aritprog.rst'
|
||||
TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'
|
||||
|
||||
|
||||
def main(argv):
|
||||
verbose = '-v' in argv
|
||||
for module_file_name in sorted(glob.glob(TARGET_GLOB)):
|
||||
module_name = module_file_name.replace('.py', '')
|
||||
module = importlib.import_module(module_name)
|
||||
gen_factory = getattr(module, 'ArithmeticProgression', None)
|
||||
if gen_factory is None:
|
||||
gen_factory = getattr(module, 'aritprog_gen', None)
|
||||
if gen_factory is None:
|
||||
continue
|
||||
|
||||
test(gen_factory, verbose)
|
||||
|
||||
|
||||
def test(gen_factory, verbose=False):
|
||||
res = doctest.testfile(
|
||||
TEST_FILE,
|
||||
globs={'aritprog_gen': gen_factory},
|
||||
verbose=verbose,
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
|
||||
tag = 'FAIL' if res.failed else 'OK'
|
||||
print(TEST_MSG.format(gen_factory.__module__, res, tag))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
main(sys.argv)
|
||||
@@ -1,25 +0,0 @@
|
||||
"""
|
||||
Arithmetic progression class
|
||||
|
||||
>>> ap = ArithmeticProgression(1, .5, 3)
|
||||
>>> list(ap)
|
||||
[1.0, 1.5, 2.0, 2.5]
|
||||
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class ArithmeticProgression:
|
||||
|
||||
def __init__(self, begin, step, end=None):
|
||||
self.begin = begin
|
||||
self.step = step
|
||||
self.end = end # None -> "infinite" series
|
||||
|
||||
def __iter__(self):
|
||||
result_type = type(self.begin + self.step)
|
||||
result = result_type(self.begin)
|
||||
forever = self.end is None
|
||||
while forever or result < self.end:
|
||||
yield result
|
||||
result += self.step
|
||||
@@ -1,45 +0,0 @@
|
||||
"""
|
||||
Arithmetic progression class
|
||||
|
||||
# tag::ARITPROG_CLASS_DEMO[]
|
||||
|
||||
>>> ap = ArithmeticProgression(0, 1, 3)
|
||||
>>> list(ap)
|
||||
[0, 1, 2]
|
||||
>>> ap = ArithmeticProgression(1, .5, 3)
|
||||
>>> list(ap)
|
||||
[1.0, 1.5, 2.0, 2.5]
|
||||
>>> ap = ArithmeticProgression(0, 1/3, 1)
|
||||
>>> list(ap)
|
||||
[0.0, 0.3333333333333333, 0.6666666666666666]
|
||||
>>> from fractions import Fraction
|
||||
>>> ap = ArithmeticProgression(0, Fraction(1, 3), 1)
|
||||
>>> list(ap)
|
||||
[Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)]
|
||||
>>> from decimal import Decimal
|
||||
>>> ap = ArithmeticProgression(0, Decimal('.1'), .3)
|
||||
>>> list(ap)
|
||||
[Decimal('0.0'), Decimal('0.1'), Decimal('0.2')]
|
||||
|
||||
# end::ARITPROG_CLASS_DEMO[]
|
||||
"""
|
||||
|
||||
|
||||
# tag::ARITPROG_CLASS[]
|
||||
class ArithmeticProgression:
|
||||
|
||||
def __init__(self, begin, step, end=None): # <1>
|
||||
self.begin = begin
|
||||
self.step = step
|
||||
self.end = end # None -> "infinite" series
|
||||
|
||||
def __iter__(self):
|
||||
result_type = type(self.begin + self.step) # <2>
|
||||
result = result_type(self.begin) # <3>
|
||||
forever = self.end is None # <4>
|
||||
index = 0
|
||||
while forever or result < self.end: # <5>
|
||||
yield result # <6>
|
||||
index += 1
|
||||
result = self.begin + self.step * index # <7>
|
||||
# end::ARITPROG_CLASS[]
|
||||
@@ -1,31 +0,0 @@
|
||||
"""
|
||||
Arithmetic progression generator function::
|
||||
|
||||
>>> ap = aritprog_gen(1, .5, 3)
|
||||
>>> list(ap)
|
||||
[1.0, 1.5, 2.0, 2.5]
|
||||
>>> ap = aritprog_gen(0, 1/3, 1)
|
||||
>>> list(ap)
|
||||
[0.0, 0.3333333333333333, 0.6666666666666666]
|
||||
>>> from fractions import Fraction
|
||||
>>> ap = aritprog_gen(0, Fraction(1, 3), 1)
|
||||
>>> list(ap)
|
||||
[Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)]
|
||||
>>> from decimal import Decimal
|
||||
>>> ap = aritprog_gen(0, Decimal('.1'), .3)
|
||||
>>> list(ap)
|
||||
[Decimal('0.0'), Decimal('0.1'), Decimal('0.2')]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# tag::ARITPROG_GENFUNC[]
|
||||
def aritprog_gen(begin, step, end=None):
|
||||
result = type(begin + step)(begin)
|
||||
forever = end is None
|
||||
index = 0
|
||||
while forever or result < end:
|
||||
yield result
|
||||
index += 1
|
||||
result = begin + step * index
|
||||
# end::ARITPROG_GENFUNC[]
|
||||
@@ -1,11 +0,0 @@
|
||||
# tag::ARITPROG_ITERTOOLS[]
|
||||
import itertools
|
||||
|
||||
|
||||
def aritprog_gen(begin, step, end=None):
|
||||
first = type(begin + step)(begin)
|
||||
ap_gen = itertools.count(first, step)
|
||||
if end is not None:
|
||||
ap_gen = itertools.takewhile(lambda n: n < end, ap_gen)
|
||||
return ap_gen
|
||||
# end::ARITPROG_ITERTOOLS[]
|
||||
@@ -1,51 +0,0 @@
|
||||
"""
|
||||
Fibonacci generator implemented "by hand" without generator objects
|
||||
|
||||
>>> from itertools import islice
|
||||
>>> list(islice(Fibonacci(), 15))
|
||||
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# tag::FIBO_BY_HAND[]
|
||||
class Fibonacci:
|
||||
|
||||
def __iter__(self):
|
||||
return FibonacciGenerator()
|
||||
|
||||
|
||||
class FibonacciGenerator:
|
||||
|
||||
def __init__(self):
|
||||
self.a = 0
|
||||
self.b = 1
|
||||
|
||||
def __next__(self):
|
||||
result = self.a
|
||||
self.a, self.b = self.b, self.a + self.b
|
||||
return result
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
# end::FIBO_BY_HAND[]
|
||||
|
||||
# for comparison, this is the usual implementation of a Fibonacci
|
||||
# generator in Python:
|
||||
|
||||
|
||||
def fibonacci():
|
||||
a, b = 0, 1
|
||||
while True:
|
||||
yield a
|
||||
a, b = b, a + b
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
for x, y in zip(Fibonacci(), fibonacci()):
|
||||
assert x == y, '%s != %s' % (x, y)
|
||||
print(x)
|
||||
if x > 10**10:
|
||||
break
|
||||
print('etc...')
|
||||
@@ -1,12 +0,0 @@
|
||||
isis2json.py
|
||||
============
|
||||
|
||||
This directory contains a copy of the ``isis2json.py`` script, with
|
||||
minimal dependencies, just to allow the O'Reilly Atlas toolchain to
|
||||
render the listing of the script in appendix A of the book.
|
||||
|
||||
If you want to use or contribute to this script, please get the full
|
||||
source code with all dependencies from the main ``isis2json``
|
||||
repository:
|
||||
|
||||
https://github.com/fluentpython/isis2json
|
||||
@@ -1,263 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
# isis2json.py: convert ISIS and ISO-2709 files to JSON
|
||||
#
|
||||
# Copyright (C) 2010 BIREME/PAHO/WHO
|
||||
#
|
||||
# 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 2.1 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 Lesser 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/>.
|
||||
|
||||
############################
|
||||
# BEGIN ISIS2JSON
|
||||
# this script works with Python or Jython (versions >=2.5 and <3)
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from uuid import uuid4
|
||||
import os
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
if os.name == 'java': # running Jython
|
||||
from com.xhaus.jyson import JysonCodec as json
|
||||
else:
|
||||
import simplejson as json
|
||||
|
||||
SKIP_INACTIVE = True
|
||||
DEFAULT_QTY = 2**31
|
||||
ISIS_MFN_KEY = 'mfn'
|
||||
ISIS_ACTIVE_KEY = 'active'
|
||||
SUBFIELD_DELIMITER = '^'
|
||||
INPUT_ENCODING = 'cp1252'
|
||||
|
||||
|
||||
def iter_iso_records(iso_file_name, isis_json_type): # <1>
|
||||
from iso2709 import IsoFile
|
||||
from subfield import expand
|
||||
|
||||
iso = IsoFile(iso_file_name)
|
||||
for record in iso:
|
||||
fields = {}
|
||||
for field in record.directory:
|
||||
field_key = str(int(field.tag)) # remove leading zeroes
|
||||
field_occurrences = fields.setdefault(field_key, [])
|
||||
content = field.value.decode(INPUT_ENCODING, 'replace')
|
||||
if isis_json_type == 1:
|
||||
field_occurrences.append(content)
|
||||
elif isis_json_type == 2:
|
||||
field_occurrences.append(expand(content))
|
||||
elif isis_json_type == 3:
|
||||
field_occurrences.append(dict(expand(content)))
|
||||
else:
|
||||
raise NotImplementedError('ISIS-JSON type %s conversion '
|
||||
'not yet implemented for .iso input' % isis_json_type)
|
||||
|
||||
yield fields
|
||||
iso.close()
|
||||
|
||||
|
||||
def iter_mst_records(master_file_name, isis_json_type): # <2>
|
||||
try:
|
||||
from bruma.master import MasterFactory, Record
|
||||
except ImportError:
|
||||
print('IMPORT ERROR: Jython 2.5 and Bruma.jar '
|
||||
'are required to read .mst files')
|
||||
raise SystemExit
|
||||
mst = MasterFactory.getInstance(master_file_name).open()
|
||||
for record in mst:
|
||||
fields = {}
|
||||
if SKIP_INACTIVE:
|
||||
if record.getStatus() != Record.Status.ACTIVE:
|
||||
continue
|
||||
else: # save status only there are non-active records
|
||||
fields[ISIS_ACTIVE_KEY] = (record.getStatus() ==
|
||||
Record.Status.ACTIVE)
|
||||
fields[ISIS_MFN_KEY] = record.getMfn()
|
||||
for field in record.getFields():
|
||||
field_key = str(field.getId())
|
||||
field_occurrences = fields.setdefault(field_key, [])
|
||||
if isis_json_type == 3:
|
||||
content = {}
|
||||
for subfield in field.getSubfields():
|
||||
subfield_key = subfield.getId()
|
||||
if subfield_key == '*':
|
||||
content['_'] = subfield.getContent()
|
||||
else:
|
||||
subfield_occurrences = content.setdefault(subfield_key, [])
|
||||
subfield_occurrences.append(subfield.getContent())
|
||||
field_occurrences.append(content)
|
||||
elif isis_json_type == 1:
|
||||
content = []
|
||||
for subfield in field.getSubfields():
|
||||
subfield_key = subfield.getId()
|
||||
if subfield_key == '*':
|
||||
content.insert(0, subfield.getContent())
|
||||
else:
|
||||
content.append(SUBFIELD_DELIMITER + subfield_key +
|
||||
subfield.getContent())
|
||||
field_occurrences.append(''.join(content))
|
||||
else:
|
||||
raise NotImplementedError('ISIS-JSON type %s conversion '
|
||||
'not yet implemented for .mst input' % isis_json_type)
|
||||
yield fields
|
||||
mst.close()
|
||||
|
||||
|
||||
def write_json(input_gen, file_name, output, qty, skip, id_tag, # <3>
|
||||
gen_uuid, mongo, mfn, isis_json_type, prefix,
|
||||
constant):
|
||||
start = skip
|
||||
end = start + qty
|
||||
if id_tag:
|
||||
id_tag = str(id_tag)
|
||||
ids = set()
|
||||
else:
|
||||
id_tag = ''
|
||||
for i, record in enumerate(input_gen):
|
||||
if i >= end:
|
||||
break
|
||||
if not mongo:
|
||||
if i == 0:
|
||||
output.write('[')
|
||||
elif i > start:
|
||||
output.write(',')
|
||||
if start <= i < end:
|
||||
if id_tag:
|
||||
occurrences = record.get(id_tag, None)
|
||||
if occurrences is None:
|
||||
msg = 'id tag #%s not found in record %s'
|
||||
if ISIS_MFN_KEY in record:
|
||||
msg = msg + (' (mfn=%s)' % record[ISIS_MFN_KEY])
|
||||
raise KeyError(msg % (id_tag, i))
|
||||
if len(occurrences) > 1:
|
||||
msg = 'multiple id tags #%s found in record %s'
|
||||
if ISIS_MFN_KEY in record:
|
||||
msg = msg + (' (mfn=%s)' % record[ISIS_MFN_KEY])
|
||||
raise TypeError(msg % (id_tag, i))
|
||||
else: # ok, we have one and only one id field
|
||||
if isis_json_type == 1:
|
||||
id = occurrences[0]
|
||||
elif isis_json_type == 2:
|
||||
id = occurrences[0][0][1]
|
||||
elif isis_json_type == 3:
|
||||
id = occurrences[0]['_']
|
||||
if id in ids:
|
||||
msg = 'duplicate id %s in tag #%s, record %s'
|
||||
if ISIS_MFN_KEY in record:
|
||||
msg = msg + (' (mfn=%s)' % record[ISIS_MFN_KEY])
|
||||
raise TypeError(msg % (id, id_tag, i))
|
||||
record['_id'] = id
|
||||
ids.add(id)
|
||||
elif gen_uuid:
|
||||
record['_id'] = unicode(uuid4())
|
||||
elif mfn:
|
||||
record['_id'] = record[ISIS_MFN_KEY]
|
||||
if prefix:
|
||||
# iterate over a fixed sequence of tags
|
||||
for tag in tuple(record):
|
||||
if str(tag).isdigit():
|
||||
record[prefix+tag] = record[tag]
|
||||
del record[tag] # this is why we iterate over a tuple
|
||||
# with the tags, and not directly on the record dict
|
||||
if constant:
|
||||
constant_key, constant_value = constant.split(':')
|
||||
record[constant_key] = constant_value
|
||||
output.write(json.dumps(record).encode('utf-8'))
|
||||
output.write('\n')
|
||||
if not mongo:
|
||||
output.write(']\n')
|
||||
|
||||
|
||||
def main(): # <4>
|
||||
# create the parser
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Convert an ISIS .mst or .iso file to a JSON array')
|
||||
|
||||
# add the arguments
|
||||
parser.add_argument(
|
||||
'file_name', metavar='INPUT.(mst|iso)',
|
||||
help='.mst or .iso file to read')
|
||||
parser.add_argument(
|
||||
'-o', '--out', type=argparse.FileType('w'), default=sys.stdout,
|
||||
metavar='OUTPUT.json',
|
||||
help='the file where the JSON output should be written'
|
||||
' (default: write to stdout)')
|
||||
parser.add_argument(
|
||||
'-c', '--couch', action='store_true',
|
||||
help='output array within a "docs" item in a JSON document'
|
||||
' for bulk insert to CouchDB via POST to db/_bulk_docs')
|
||||
parser.add_argument(
|
||||
'-m', '--mongo', action='store_true',
|
||||
help='output individual records as separate JSON dictionaries, one'
|
||||
' per line for bulk insert to MongoDB via mongoimport utility')
|
||||
parser.add_argument(
|
||||
'-t', '--type', type=int, metavar='ISIS_JSON_TYPE', default=1,
|
||||
help='ISIS-JSON type, sets field structure: 1=string, 2=alist,'
|
||||
' 3=dict (default=1)')
|
||||
parser.add_argument(
|
||||
'-q', '--qty', type=int, default=DEFAULT_QTY,
|
||||
help='maximum quantity of records to read (default=ALL)')
|
||||
parser.add_argument(
|
||||
'-s', '--skip', type=int, default=0,
|
||||
help='records to skip from start of .mst (default=0)')
|
||||
parser.add_argument(
|
||||
'-i', '--id', type=int, metavar='TAG_NUMBER', default=0,
|
||||
help='generate an "_id" from the given unique TAG field number'
|
||||
' for each record')
|
||||
parser.add_argument(
|
||||
'-u', '--uuid', action='store_true',
|
||||
help='generate an "_id" with a random UUID for each record')
|
||||
parser.add_argument(
|
||||
'-p', '--prefix', type=str, metavar='PREFIX', default='',
|
||||
help='concatenate prefix to every numeric field tag'
|
||||
' (ex. 99 becomes "v99")')
|
||||
parser.add_argument(
|
||||
'-n', '--mfn', action='store_true',
|
||||
help='generate an "_id" from the MFN of each record'
|
||||
' (available only for .mst input)')
|
||||
parser.add_argument(
|
||||
'-k', '--constant', type=str, metavar='TAG:VALUE', default='',
|
||||
help='Include a constant tag:value in every record (ex. -k type:AS)')
|
||||
|
||||
'''
|
||||
# TODO: implement this to export large quantities of records to CouchDB
|
||||
parser.add_argument(
|
||||
'-r', '--repeat', type=int, default=1,
|
||||
help='repeat operation, saving multiple JSON files'
|
||||
' (default=1, use -r 0 to repeat until end of input)')
|
||||
'''
|
||||
# parse the command line
|
||||
args = parser.parse_args()
|
||||
if args.file_name.lower().endswith('.mst'):
|
||||
input_gen_func = iter_mst_records # <5>
|
||||
else:
|
||||
if args.mfn:
|
||||
print('UNSUPORTED: -n/--mfn option only available for .mst input.')
|
||||
raise SystemExit
|
||||
input_gen_func = iter_iso_records # <6>
|
||||
input_gen = input_gen_func(args.file_name, args.type) # <7>
|
||||
if args.couch:
|
||||
args.out.write('{ "docs" : ')
|
||||
write_json(input_gen, args.file_name, args.out, args.qty, # <8>
|
||||
args.skip, args.id, args.uuid, args.mongo, args.mfn,
|
||||
args.type, args.prefix, args.constant)
|
||||
if args.couch:
|
||||
args.out.write('}\n')
|
||||
args.out.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
# END ISIS2JSON
|
||||
@@ -1,167 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
# ISO-2709 file reader
|
||||
#
|
||||
# Copyright (C) 2010 BIREME/PAHO/WHO
|
||||
#
|
||||
# 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 2.1 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 Lesser 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/>.
|
||||
|
||||
from struct import unpack
|
||||
|
||||
CR = '\x0D' # \r
|
||||
LF = '\x0A' # \n
|
||||
IS1 = '\x1F' # ECMA-48 Unit Separator
|
||||
IS2 = '\x1E' # ECMA-48 Record Separator / ISO-2709 field separator
|
||||
IS3 = '\x1D' # ECMA-48 Group Separator / ISO-2709 record separator
|
||||
LABEL_LEN = 24
|
||||
LABEL_FORMAT = '5s c 4s c c 5s 3s c c c c'
|
||||
TAG_LEN = 3
|
||||
DEFAULT_ENCODING = 'ASCII'
|
||||
SUBFIELD_DELIMITER = '^'
|
||||
|
||||
class IsoFile(object):
|
||||
|
||||
def __init__(self, filename, encoding = DEFAULT_ENCODING):
|
||||
self.file = open(filename, 'rb')
|
||||
self.encoding = encoding
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
return IsoRecord(self)
|
||||
|
||||
__next__ = next # Python 3 compatibility
|
||||
|
||||
def read(self, size):
|
||||
''' read and drop all CR and LF characters '''
|
||||
# TODO: this is inneficient but works, patches accepted!
|
||||
# NOTE: our fixtures include files which have no linebreaks,
|
||||
# files with CR-LF linebreaks and files with LF linebreaks
|
||||
chunks = []
|
||||
count = 0
|
||||
while count < size:
|
||||
chunk = self.file.read(size-count)
|
||||
if len(chunk) == 0:
|
||||
break
|
||||
chunk = chunk.replace(CR+LF,'')
|
||||
if CR in chunk:
|
||||
chunk = chunk.replace(CR,'')
|
||||
if LF in chunk:
|
||||
chunk = chunk.replace(LF,'')
|
||||
count += len(chunk)
|
||||
chunks.append(chunk)
|
||||
return ''.join(chunks)
|
||||
|
||||
def close(self):
|
||||
self.file.close()
|
||||
|
||||
class IsoRecord(object):
|
||||
label_part_names = ('rec_len rec_status impl_codes indicator_len identifier_len'
|
||||
' base_addr user_defined'
|
||||
# directory map:
|
||||
' fld_len_len start_len impl_len reserved').split()
|
||||
rec_len = 0
|
||||
|
||||
def __init__(self, iso_file=None):
|
||||
self.iso_file = iso_file
|
||||
self.load_label()
|
||||
self.load_directory()
|
||||
self.load_fields()
|
||||
|
||||
def __len__(self):
|
||||
return self.rec_len
|
||||
|
||||
def load_label(self):
|
||||
label = self.iso_file.read(LABEL_LEN)
|
||||
if len(label) == 0:
|
||||
raise StopIteration
|
||||
elif len(label) != 24:
|
||||
raise ValueError('Invalid record label: "%s"' % label)
|
||||
parts = unpack(LABEL_FORMAT, label)
|
||||
for name, part in zip(self.label_part_names, parts):
|
||||
if name.endswith('_len') or name.endswith('_addr'):
|
||||
part = int(part)
|
||||
setattr(self, name, part)
|
||||
|
||||
def show_label(self):
|
||||
for name in self.label_part_names:
|
||||
print('%15s : %r' % (name, getattr(self, name)))
|
||||
|
||||
def load_directory(self):
|
||||
fmt_dir = '3s %ss %ss %ss' % (self.fld_len_len, self.start_len, self.impl_len)
|
||||
entry_len = TAG_LEN + self.fld_len_len + self.start_len + self.impl_len
|
||||
self.directory = []
|
||||
while True:
|
||||
char = self.iso_file.read(1)
|
||||
if char.isdigit():
|
||||
entry = char + self.iso_file.read(entry_len-1)
|
||||
entry = Field(* unpack(fmt_dir, entry))
|
||||
self.directory.append(entry)
|
||||
else:
|
||||
break
|
||||
|
||||
def load_fields(self):
|
||||
for field in self.directory:
|
||||
if self.indicator_len > 0:
|
||||
field.indicator = self.iso_file.read(self.indicator_len)
|
||||
# XXX: lilacs30.iso has an identifier_len == 2,
|
||||
# but we need to ignore it to succesfully read the field contents
|
||||
# TODO: find out when to ignore the idenfier_len,
|
||||
# or fix the lilacs30.iso fixture
|
||||
#
|
||||
##if self.identifier_len > 0: #
|
||||
## field.identifier = self.iso_file.read(self.identifier_len)
|
||||
value = self.iso_file.read(len(field))
|
||||
assert len(value) == len(field)
|
||||
field.value = value[:-1] # remove trailing field separator
|
||||
self.iso_file.read(1) # discard record separator
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
for field in self.directory:
|
||||
yield(field)
|
||||
|
||||
__next__ = next # Python 3 compatibility
|
||||
|
||||
def dump(self):
|
||||
for field in self.directory:
|
||||
print('%3s %r' % (field.tag, field.value))
|
||||
|
||||
class Field(object):
|
||||
|
||||
def __init__(self, tag, len, start, impl):
|
||||
self.tag = tag
|
||||
self.len = int(len)
|
||||
self.start = int(start)
|
||||
self.impl = impl
|
||||
|
||||
def show(self):
|
||||
for name in 'tag len start impl'.split():
|
||||
print('%15s : %r' % (name, getattr(self, name)))
|
||||
|
||||
def __len__(self):
|
||||
return self.len
|
||||
|
||||
def test():
|
||||
import doctest
|
||||
doctest.testfile('iso2709_test.txt')
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
# ISIS-DM: the ISIS Data Model API
|
||||
#
|
||||
# Copyright (C) 2010 BIREME/PAHO/WHO
|
||||
#
|
||||
# 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 2.1 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 Lesser 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/>.
|
||||
|
||||
from collections import namedtuple
|
||||
import re
|
||||
|
||||
|
||||
MAIN_SUBFIELD_KEY = '_'
|
||||
SUBFIELD_MARKER_RE = re.compile(r'\^([a-z0-9])', re.IGNORECASE)
|
||||
DEFAULT_ENCODING = u'utf-8'
|
||||
|
||||
def expand(content, subkeys=None):
|
||||
''' Parse a field into an association list of keys and subfields
|
||||
|
||||
>>> expand('zero^1one^2two^3three')
|
||||
[('_', 'zero'), ('1', 'one'), ('2', 'two'), ('3', 'three')]
|
||||
|
||||
'''
|
||||
if subkeys is None:
|
||||
regex = SUBFIELD_MARKER_RE
|
||||
elif subkeys == '':
|
||||
return [(MAIN_SUBFIELD_KEY, content)]
|
||||
else:
|
||||
regex = re.compile(r'\^(['+subkeys+'])', re.IGNORECASE)
|
||||
content = content.replace('^^', '^^ ')
|
||||
parts = []
|
||||
start = 0
|
||||
key = MAIN_SUBFIELD_KEY
|
||||
while True:
|
||||
found = regex.search(content, start)
|
||||
if found is None: break
|
||||
parts.append((key, content[start:found.start()].rstrip()))
|
||||
key = found.group(1).lower()
|
||||
start = found.end()
|
||||
parts.append((key, content[start:].rstrip()))
|
||||
return parts
|
||||
|
||||
|
||||
class CompositeString(object):
|
||||
''' Represent an Isis field, with subfields, using
|
||||
Python native datastructures
|
||||
|
||||
>>> author = CompositeString('John Tenniel^xillustrator',
|
||||
... subkeys='x')
|
||||
>>> unicode(author)
|
||||
u'John Tenniel^xillustrator'
|
||||
'''
|
||||
|
||||
def __init__(self, isis_raw, subkeys=None, encoding=DEFAULT_ENCODING):
|
||||
if not isinstance(isis_raw, basestring):
|
||||
raise TypeError('%r value must be unicode or str instance' % isis_raw)
|
||||
|
||||
self.__isis_raw = isis_raw.decode(encoding)
|
||||
self.__expanded = expand(self.__isis_raw, subkeys)
|
||||
|
||||
def __getitem__(self, key):
|
||||
for subfield in self.__expanded:
|
||||
if subfield[0] == key:
|
||||
return subfield[1]
|
||||
else:
|
||||
raise KeyError(key)
|
||||
|
||||
def __iter__(self):
|
||||
return (subfield[0] for subfield in self.__expanded)
|
||||
|
||||
def items(self):
|
||||
return self.__expanded
|
||||
|
||||
def __unicode__(self):
|
||||
return self.__isis_raw
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__isis_raw)
|
||||
|
||||
|
||||
class CompositeField(object):
|
||||
''' Represent an Isis field, with subfields, using
|
||||
Python native datastructures
|
||||
|
||||
>>> author = CompositeField( [('name','Braz, Marcelo'),('role','writer')] )
|
||||
>>> print author['name']
|
||||
Braz, Marcelo
|
||||
>>> print author['role']
|
||||
writer
|
||||
>>> author
|
||||
CompositeField((('name', 'Braz, Marcelo'), ('role', 'writer')))
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, value, subkeys=None):
|
||||
if subkeys is None:
|
||||
subkeys = [item[0] for item in value]
|
||||
try:
|
||||
value_as_dict = dict(value)
|
||||
except TypeError:
|
||||
raise TypeError('%r value must be a key-value structure' % self)
|
||||
|
||||
for key in value_as_dict:
|
||||
if key not in subkeys:
|
||||
raise TypeError('Unexpected keyword %r' % key)
|
||||
|
||||
self.value = tuple([(key, value_as_dict.get(key,None)) for key in subkeys])
|
||||
|
||||
def __getitem__(self, key):
|
||||
return dict(self.value)[key]
|
||||
|
||||
def __repr__(self):
|
||||
return "CompositeField(%s)" % str(self.items())
|
||||
|
||||
def items(self):
|
||||
return self.value
|
||||
|
||||
def __unicode__(self):
|
||||
unicode(self.items())
|
||||
|
||||
def __str__(self):
|
||||
str(self.items())
|
||||
|
||||
|
||||
def test():
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
if __name__=='__main__':
|
||||
test()
|
||||
@@ -1,37 +0,0 @@
|
||||
"""
|
||||
Sentence: access words by index
|
||||
|
||||
>>> text = 'To be, or not to be, that is the question'
|
||||
>>> s = Sentence(text)
|
||||
>>> len(s)
|
||||
10
|
||||
>>> s[1], s[5]
|
||||
('be', 'be')
|
||||
>>> s
|
||||
Sentence('To be, or no... the question')
|
||||
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_SEQ[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile(r'\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
self.words = RE_WORD.findall(text) # <1>
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.words[index] # <2>
|
||||
|
||||
def __len__(self): # <3>
|
||||
return len(self.words)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text) # <4>
|
||||
|
||||
# end::SENTENCE_SEQ[]
|
||||
@@ -1,54 +0,0 @@
|
||||
==============================
|
||||
Tests for a ``Sentence`` class
|
||||
==============================
|
||||
|
||||
A ``Sentence`` is built from a ``str`` and allows iteration
|
||||
word-by-word.
|
||||
|
||||
::
|
||||
>>> s = Sentence('The time has come')
|
||||
>>> s
|
||||
Sentence('The time has come')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come']
|
||||
>>> it = iter(s)
|
||||
>>> next(it)
|
||||
'The'
|
||||
>>> next(it)
|
||||
'time'
|
||||
>>> next(it)
|
||||
'has'
|
||||
>>> next(it)
|
||||
'come'
|
||||
>>> next(it)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
StopIteration
|
||||
|
||||
|
||||
Any punctuation is skipped while iterating::
|
||||
|
||||
>>> s = Sentence('"The time has come," the Walrus said,')
|
||||
>>> s
|
||||
Sentence('"The time ha... Walrus said,')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
|
||||
|
||||
|
||||
White space including line breaks are also ignored::
|
||||
|
||||
>>> s = Sentence('''"The time has come," the Walrus said,
|
||||
... "To talk of many things:"''')
|
||||
>>> s
|
||||
Sentence('"The time ha...many things:"')
|
||||
>>> list(s)
|
||||
['The', 'time', 'has', 'come', 'the', 'Walrus', 'said', 'To', 'talk', 'of', 'many', 'things']
|
||||
|
||||
|
||||
Accented Latin characters are also recognized as word characters::
|
||||
|
||||
>>> s = Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> s
|
||||
Sentence('Agora vou-me. Ou me vão?')
|
||||
>>> list(s)
|
||||
['Agora', 'vou', 'me', 'Ou', 'me', 'vão']
|
||||
@@ -1,28 +0,0 @@
|
||||
"""
|
||||
Sentence: iterate over words using a generator function
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_GEN[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile(r'\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
self.words = RE_WORD.findall(text)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self):
|
||||
for word in self.words: # <1>
|
||||
yield word # <2>
|
||||
return # <3>
|
||||
|
||||
# done! <4>
|
||||
|
||||
# end::SENTENCE_GEN[]
|
||||
@@ -1,24 +0,0 @@
|
||||
"""
|
||||
Sentence: iterate over words using a generator function
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_GEN2[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile('r\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text # <1>
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self):
|
||||
for match in RE_WORD.finditer(self.text): # <2>
|
||||
yield match.group() # <3>
|
||||
|
||||
# end::SENTENCE_GEN2[]
|
||||
@@ -1,44 +0,0 @@
|
||||
"""
|
||||
Sentence: iterate over words using a generator expression
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_GENEXP[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile(r'\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self):
|
||||
return (match.group() for match in RE_WORD.finditer(self.text))
|
||||
# end::SENTENCE_GENEXP[]
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
import warnings
|
||||
try:
|
||||
filename = sys.argv[1]
|
||||
word_number = int(sys.argv[2])
|
||||
except (IndexError, ValueError):
|
||||
print('Usage: %s <file-name> <word-number>' % sys.argv[0])
|
||||
sys.exit(1)
|
||||
with open(filename, 'rt', encoding='utf-8') as text_file:
|
||||
s = Sentence(text_file.read())
|
||||
for n, word in enumerate(s, 1):
|
||||
if n == word_number:
|
||||
print(word)
|
||||
break
|
||||
else:
|
||||
warnings.warn('last word is #%d, "%s"' % (n, word))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,65 +0,0 @@
|
||||
"""
|
||||
Sentence: iterate over words using the Iterator Pattern, take #1
|
||||
|
||||
WARNING: the Iterator Pattern is much simpler in idiomatic Python;
|
||||
see: sentence_gen*.py.
|
||||
"""
|
||||
|
||||
# tag::SENTENCE_ITER[]
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile(r'\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
self.words = RE_WORD.findall(text)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self): # <1>
|
||||
return SentenceIterator(self.words) # <2>
|
||||
|
||||
|
||||
class SentenceIterator:
|
||||
|
||||
def __init__(self, words):
|
||||
self.words = words # <3>
|
||||
self.index = 0 # <4>
|
||||
|
||||
def __next__(self):
|
||||
try:
|
||||
word = self.words[self.index] # <5>
|
||||
except IndexError:
|
||||
raise StopIteration() # <6>
|
||||
self.index += 1 # <7>
|
||||
return word # <8>
|
||||
|
||||
def __iter__(self): # <9>
|
||||
return self
|
||||
# end::SENTENCE_ITER[]
|
||||
|
||||
def main():
|
||||
import sys
|
||||
import warnings
|
||||
try:
|
||||
filename = sys.argv[1]
|
||||
word_number = int(sys.argv[2])
|
||||
except (IndexError, ValueError):
|
||||
print('Usage: %s <file-name> <word-number>' % sys.argv[0])
|
||||
sys.exit(1)
|
||||
with open(filename, 'rt', encoding='utf-8') as text_file:
|
||||
s = Sentence(text_file.read())
|
||||
for n, word in enumerate(s, 1):
|
||||
if n == word_number:
|
||||
print(word)
|
||||
break
|
||||
else:
|
||||
warnings.warn('last word is #%d, "%s"' % (n, word))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,37 +0,0 @@
|
||||
"""
|
||||
Sentence: iterate over words using the Iterator Pattern, take #2
|
||||
|
||||
WARNING: the Iterator Pattern is much simpler in idiomatic Python;
|
||||
see: sentence_gen*.py.
|
||||
"""
|
||||
|
||||
import re
|
||||
import reprlib
|
||||
|
||||
RE_WORD = re.compile(r'\w+')
|
||||
|
||||
|
||||
class Sentence:
|
||||
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
|
||||
def __repr__(self):
|
||||
return 'Sentence(%s)' % reprlib.repr(self.text)
|
||||
|
||||
def __iter__(self):
|
||||
word_iter = RE_WORD.finditer(self.text) # <1>
|
||||
return SentenceIter(word_iter) # <2>
|
||||
|
||||
|
||||
class SentenceIter():
|
||||
|
||||
def __init__(self, word_iter):
|
||||
self.word_iter = word_iter # <3>
|
||||
|
||||
def __next__(self):
|
||||
match = next(self.word_iter) # <4>
|
||||
return match.group() # <5>
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
@@ -1,36 +0,0 @@
|
||||
import doctest
|
||||
import importlib
|
||||
import glob
|
||||
|
||||
|
||||
TARGET_GLOB = 'sentence*.py'
|
||||
TEST_FILE = 'sentence.rst'
|
||||
TEST_MSG = '{0:16} {1.attempted:2} tests, {1.failed:2} failed - {2}'
|
||||
|
||||
|
||||
def main(argv):
|
||||
verbose = '-v' in argv
|
||||
for module_file_name in sorted(glob.glob(TARGET_GLOB)):
|
||||
module_name = module_file_name.replace('.py', '')
|
||||
module = importlib.import_module(module_name)
|
||||
try:
|
||||
cls = getattr(module, 'Sentence')
|
||||
except AttributeError:
|
||||
continue
|
||||
test(cls, verbose)
|
||||
|
||||
|
||||
def test(cls, verbose=False):
|
||||
|
||||
res = doctest.testfile(
|
||||
TEST_FILE,
|
||||
globs={'Sentence': cls},
|
||||
verbose=verbose,
|
||||
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE)
|
||||
tag = 'FAIL' if res.failed else 'OK'
|
||||
print(TEST_MSG.format(cls.__module__, res, tag))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
main(sys.argv)
|
||||
@@ -1,7 +0,0 @@
|
||||
def tree(cls):
|
||||
yield cls.__name__
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name in tree(BaseException):
|
||||
print(cls_name)
|
||||
@@ -1,10 +0,0 @@
|
||||
def tree(cls):
|
||||
yield cls.__name__, 0
|
||||
for sub_cls in cls.__subclasses__():
|
||||
yield sub_cls.__name__, 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level in tree(BaseException):
|
||||
indent = ' ' * 4 * level
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,12 +0,0 @@
|
||||
def tree(cls):
|
||||
yield cls.__name__, 0
|
||||
for sub_cls in cls.__subclasses__():
|
||||
yield sub_cls.__name__, 1
|
||||
for sub_sub_cls in sub_cls.__subclasses__():
|
||||
yield sub_sub_cls.__name__, 2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level in tree(BaseException):
|
||||
indent = ' ' * 4 * level
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,10 +0,0 @@
|
||||
def tree(cls, level=0):
|
||||
yield cls.__name__, level
|
||||
for sub_cls in cls.__subclasses__():
|
||||
yield from tree(sub_cls, level + 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level in tree(BaseException):
|
||||
indent = ' ' * 4 * level
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,31 +0,0 @@
|
||||
from tree import tree
|
||||
|
||||
SPACES = ' ' * 4
|
||||
HLINE = '\u2500' # ─ BOX DRAWINGS LIGHT HORIZONTAL
|
||||
HLINE2 = HLINE * 2
|
||||
ELBOW = f'\u2514{HLINE2} ' # └ BOX DRAWINGS LIGHT UP AND RIGHT
|
||||
TEE = f'\u251C{HLINE2} ' # ├ BOX DRAWINGS LIGHT VERTICAL AND RIGHT
|
||||
PIPE = f'\u2502 ' # │ BOX DRAWINGS LIGHT VERTICAL
|
||||
|
||||
|
||||
def render_lines(tree_iter):
|
||||
name, _, _ = next(tree_iter)
|
||||
yield name
|
||||
prefix = ''
|
||||
|
||||
for name, level, last in tree_iter:
|
||||
if last:
|
||||
connector = ELBOW
|
||||
else:
|
||||
connector = TEE
|
||||
|
||||
prefix = prefix[:4 * (level-1)]
|
||||
prefix = prefix.replace(TEE, PIPE).replace(ELBOW, SPACES)
|
||||
prefix += connector
|
||||
|
||||
yield prefix + name
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for line in render_lines(tree(BaseException)):
|
||||
print(line)
|
||||
@@ -1,101 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from pretty_tree import tree, render_lines
|
||||
|
||||
def test_1_level():
|
||||
result = list(render_lines(tree(BrokenPipeError)))
|
||||
expected = [
|
||||
'BrokenPipeError',
|
||||
]
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_2_levels_1_leaf():
|
||||
result = list(render_lines(tree(IndentationError)))
|
||||
expected = [
|
||||
'IndentationError',
|
||||
'└── TabError',
|
||||
]
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_3_levels_1_leaf():
|
||||
class X: pass
|
||||
class Y(X): pass
|
||||
class Z(Y): pass
|
||||
result = list(render_lines(tree(X)))
|
||||
expected = [
|
||||
'X',
|
||||
'└── Y',
|
||||
' └── Z',
|
||||
]
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_1_leaf():
|
||||
class Level0: pass
|
||||
class Level1(Level0): pass
|
||||
class Level2(Level1): pass
|
||||
class Level3(Level2): pass
|
||||
|
||||
result = list(render_lines(tree(Level0)))
|
||||
expected = [
|
||||
'Level0',
|
||||
'└── Level1',
|
||||
' └── Level2',
|
||||
' └── Level3',
|
||||
]
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_2_levels_2_leaves():
|
||||
class Branch: pass
|
||||
class Leaf1(Branch): pass
|
||||
class Leaf2(Branch): pass
|
||||
result = list(render_lines(tree(Branch)))
|
||||
expected = [
|
||||
'Branch',
|
||||
'├── Leaf1',
|
||||
'└── Leaf2',
|
||||
]
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_3_levels_2_leaves():
|
||||
class A: pass
|
||||
class B(A): pass
|
||||
class C(B): pass
|
||||
class D(A): pass
|
||||
class E(D): pass
|
||||
|
||||
result = list(render_lines(tree(A)))
|
||||
expected = [
|
||||
'A',
|
||||
'├── B',
|
||||
'│ └── C',
|
||||
'└── D',
|
||||
' └── E',
|
||||
]
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_4_leaves():
|
||||
class A: pass
|
||||
class B1(A): pass
|
||||
class C1(B1): pass
|
||||
class D1(C1): pass
|
||||
class D2(C1): pass
|
||||
class C2(B1): pass
|
||||
class B2(A): pass
|
||||
expected = [
|
||||
'A',
|
||||
'├── B1',
|
||||
'│ ├── C1',
|
||||
'│ │ ├── D1',
|
||||
'│ │ └── D2',
|
||||
'│ └── C2',
|
||||
'└── B2',
|
||||
]
|
||||
|
||||
result = list(render_lines(tree(A)))
|
||||
assert expected == result
|
||||
@@ -1,90 +0,0 @@
|
||||
from tree import tree
|
||||
|
||||
|
||||
def test_1_level():
|
||||
class One: pass
|
||||
expected = [('One', 0, True)]
|
||||
result = list(tree(One))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_2_levels_2_leaves():
|
||||
class Branch: pass
|
||||
class Leaf1(Branch): pass
|
||||
class Leaf2(Branch): pass
|
||||
expected = [
|
||||
('Branch', 0, True),
|
||||
('Leaf1', 1, False),
|
||||
('Leaf2', 1, True),
|
||||
]
|
||||
result = list(tree(Branch))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_3_levels_1_leaf():
|
||||
class X: pass
|
||||
class Y(X): pass
|
||||
class Z(Y): pass
|
||||
expected = [
|
||||
('X', 0, True),
|
||||
('Y', 1, True),
|
||||
('Z', 2, True),
|
||||
]
|
||||
result = list(tree(X))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_1_leaf():
|
||||
class Level0: pass
|
||||
class Level1(Level0): pass
|
||||
class Level2(Level1): pass
|
||||
class Level3(Level2): pass
|
||||
expected = [
|
||||
('Level0', 0, True),
|
||||
('Level1', 1, True),
|
||||
('Level2', 2, True),
|
||||
('Level3', 3, True),
|
||||
]
|
||||
|
||||
result = list(tree(Level0))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_3_leaves():
|
||||
class A: pass
|
||||
class B1(A): pass
|
||||
class C1(B1): pass
|
||||
class D1(C1): pass
|
||||
class B2(A): pass
|
||||
class D2(C1): pass
|
||||
class C2(B2): pass
|
||||
expected = [
|
||||
('A', 0, True),
|
||||
('B1', 1, False),
|
||||
('C1', 2, True),
|
||||
('D1', 3, False),
|
||||
('D2', 3, True),
|
||||
('B2', 1, True),
|
||||
('C2', 2, True),
|
||||
]
|
||||
|
||||
result = list(tree(A))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_many_levels_1_leaf():
|
||||
class Root: pass
|
||||
level_count = 100
|
||||
expected = [('Root', 0, True)]
|
||||
parent = Root
|
||||
for level in range(1, level_count):
|
||||
name = f'Sub{level}'
|
||||
cls = type(name, (parent,), {})
|
||||
expected.append((name, level, True))
|
||||
parent = cls
|
||||
|
||||
result = list(tree(Root))
|
||||
assert len(result) == level_count
|
||||
assert result[0] == ('Root', 0, True)
|
||||
assert result[-1] == ('Sub99', 99, True)
|
||||
assert expected == result
|
||||
@@ -1,13 +0,0 @@
|
||||
def tree(cls, level=0, last_in_level=True):
|
||||
yield cls.__name__, level, last_in_level
|
||||
subclasses = cls.__subclasses__()
|
||||
if subclasses:
|
||||
last = subclasses[-1]
|
||||
for sub_cls in subclasses:
|
||||
yield from tree(sub_cls, level+1, sub_cls is last)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level, _ in tree(BaseException):
|
||||
indent = ' ' * 4 * level
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,8 +0,0 @@
|
||||
from tree import tree
|
||||
|
||||
|
||||
def test_1_level():
|
||||
class One: pass
|
||||
expected = ['One']
|
||||
result = list(tree(One))
|
||||
assert expected == result
|
||||
@@ -1,7 +0,0 @@
|
||||
def tree(cls):
|
||||
yield cls.__name__
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name in tree(BaseException):
|
||||
print(cls_name)
|
||||
@@ -1,21 +0,0 @@
|
||||
from tree import tree
|
||||
|
||||
|
||||
def test_1_level():
|
||||
class One: pass
|
||||
expected = [('One', 0)]
|
||||
result = list(tree(One))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_2_levels_2_leaves():
|
||||
class Branch: pass
|
||||
class Leaf1(Branch): pass
|
||||
class Leaf2(Branch): pass
|
||||
expected = [
|
||||
('Branch', 0),
|
||||
('Leaf1', 1),
|
||||
('Leaf2', 1),
|
||||
]
|
||||
result = list(tree(Branch))
|
||||
assert expected == result
|
||||
@@ -1,10 +0,0 @@
|
||||
def tree(cls):
|
||||
yield cls.__name__, 0 # <1>
|
||||
for sub_cls in cls.__subclasses__(): # <2>
|
||||
yield sub_cls.__name__, 1 # <3>
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level in tree(BaseException):
|
||||
indent = ' ' * 4 * level # <4>
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,21 +0,0 @@
|
||||
from tree import tree
|
||||
|
||||
|
||||
def test_1_level():
|
||||
class One: pass
|
||||
expected = [('One', 0)]
|
||||
result = list(tree(One))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_2_levels_2_leaves():
|
||||
class Branch: pass
|
||||
class Leaf1(Branch): pass
|
||||
class Leaf2(Branch): pass
|
||||
expected = [
|
||||
('Branch', 0),
|
||||
('Leaf1', 1),
|
||||
('Leaf2', 1),
|
||||
]
|
||||
result = list(tree(Branch))
|
||||
assert expected == result
|
||||
@@ -1,14 +0,0 @@
|
||||
def tree(cls):
|
||||
yield cls.__name__, 0
|
||||
yield from sub_tree(cls) # <1>
|
||||
|
||||
|
||||
def sub_tree(cls):
|
||||
for sub_cls in cls.__subclasses__():
|
||||
yield sub_cls.__name__, 1 # <2>
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level in tree(BaseException):
|
||||
indent = ' ' * 4 * level
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,34 +0,0 @@
|
||||
from tree import tree
|
||||
|
||||
|
||||
def test_1_level():
|
||||
class One: pass
|
||||
expected = [('One', 0)]
|
||||
result = list(tree(One))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_2_levels_2_leaves():
|
||||
class Branch: pass
|
||||
class Leaf1(Branch): pass
|
||||
class Leaf2(Branch): pass
|
||||
expected = [
|
||||
('Branch', 0),
|
||||
('Leaf1', 1),
|
||||
('Leaf2', 1),
|
||||
]
|
||||
result = list(tree(Branch))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_3_levels_1_leaf():
|
||||
class X: pass
|
||||
class Y(X): pass
|
||||
class Z(Y): pass
|
||||
expected = [
|
||||
('X', 0),
|
||||
('Y', 1),
|
||||
('Z', 2),
|
||||
]
|
||||
result = list(tree(X))
|
||||
assert expected == result
|
||||
@@ -1,16 +0,0 @@
|
||||
def tree(cls):
|
||||
yield cls.__name__, 0
|
||||
yield from sub_tree(cls)
|
||||
|
||||
|
||||
def sub_tree(cls):
|
||||
for sub_cls in cls.__subclasses__():
|
||||
yield sub_cls.__name__, 1
|
||||
for sub_sub_cls in sub_cls.__subclasses__():
|
||||
yield sub_sub_cls.__name__, 2
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level in tree(BaseException):
|
||||
indent = ' ' * 4 * level
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,72 +0,0 @@
|
||||
from tree import tree
|
||||
|
||||
|
||||
def test_1_level():
|
||||
class One: pass
|
||||
expected = [('One', 0)]
|
||||
result = list(tree(One))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_2_levels_2_leaves():
|
||||
class Branch: pass
|
||||
class Leaf1(Branch): pass
|
||||
class Leaf2(Branch): pass
|
||||
expected = [
|
||||
('Branch', 0),
|
||||
('Leaf1', 1),
|
||||
('Leaf2', 1),
|
||||
]
|
||||
result = list(tree(Branch))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_3_levels_1_leaf():
|
||||
class X: pass
|
||||
class Y(X): pass
|
||||
class Z(Y): pass
|
||||
expected = [
|
||||
('X', 0),
|
||||
('Y', 1),
|
||||
('Z', 2),
|
||||
]
|
||||
result = list(tree(X))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_1_leaf():
|
||||
class Level0: pass
|
||||
class Level1(Level0): pass
|
||||
class Level2(Level1): pass
|
||||
class Level3(Level2): pass
|
||||
expected = [
|
||||
('Level0', 0),
|
||||
('Level1', 1),
|
||||
('Level2', 2),
|
||||
('Level3', 3),
|
||||
]
|
||||
|
||||
result = list(tree(Level0))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_3_leaves():
|
||||
class A: pass
|
||||
class B1(A): pass
|
||||
class C1(B1): pass
|
||||
class D1(C1): pass
|
||||
class B2(A): pass
|
||||
class D2(C1): pass
|
||||
class C2(B2): pass
|
||||
expected = [
|
||||
('A', 0),
|
||||
('B1', 1),
|
||||
('C1', 2),
|
||||
('D1', 3),
|
||||
('D2', 3),
|
||||
('B2', 1),
|
||||
('C2', 2),
|
||||
]
|
||||
|
||||
result = list(tree(A))
|
||||
assert expected == result
|
||||
@@ -1,20 +0,0 @@
|
||||
def tree(cls):
|
||||
yield cls.__name__, 0
|
||||
yield from sub_tree(cls)
|
||||
|
||||
|
||||
# tag::SUB_TREE[]
|
||||
def sub_tree(cls):
|
||||
for sub_cls in cls.__subclasses__():
|
||||
yield sub_cls.__name__, 1
|
||||
for sub_sub_cls in sub_cls.__subclasses__():
|
||||
yield sub_sub_cls.__name__, 2
|
||||
for sub_sub_sub_cls in sub_sub_cls.__subclasses__():
|
||||
yield sub_sub_sub_cls.__name__, 3
|
||||
# end::SUB_TREE[]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level in tree(BaseException):
|
||||
indent = ' ' * 4 * level
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,90 +0,0 @@
|
||||
from tree import tree
|
||||
|
||||
|
||||
def test_1_level():
|
||||
class One: pass
|
||||
expected = [('One', 0)]
|
||||
result = list(tree(One))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_2_levels_2_leaves():
|
||||
class Branch: pass
|
||||
class Leaf1(Branch): pass
|
||||
class Leaf2(Branch): pass
|
||||
expected = [
|
||||
('Branch', 0),
|
||||
('Leaf1', 1),
|
||||
('Leaf2', 1),
|
||||
]
|
||||
result = list(tree(Branch))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_3_levels_1_leaf():
|
||||
class X: pass
|
||||
class Y(X): pass
|
||||
class Z(Y): pass
|
||||
expected = [
|
||||
('X', 0),
|
||||
('Y', 1),
|
||||
('Z', 2),
|
||||
]
|
||||
result = list(tree(X))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_1_leaf():
|
||||
class Level0: pass
|
||||
class Level1(Level0): pass
|
||||
class Level2(Level1): pass
|
||||
class Level3(Level2): pass
|
||||
expected = [
|
||||
('Level0', 0),
|
||||
('Level1', 1),
|
||||
('Level2', 2),
|
||||
('Level3', 3),
|
||||
]
|
||||
|
||||
result = list(tree(Level0))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_3_leaves():
|
||||
class A: pass
|
||||
class B1(A): pass
|
||||
class C1(B1): pass
|
||||
class D1(C1): pass
|
||||
class B2(A): pass
|
||||
class D2(C1): pass
|
||||
class C2(B2): pass
|
||||
expected = [
|
||||
('A', 0),
|
||||
('B1', 1),
|
||||
('C1', 2),
|
||||
('D1', 3),
|
||||
('D2', 3),
|
||||
('B2', 1),
|
||||
('C2', 2),
|
||||
]
|
||||
|
||||
result = list(tree(A))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_many_levels_1_leaf():
|
||||
class Root: pass
|
||||
level_count = 100
|
||||
expected = [('Root', 0)]
|
||||
parent = Root
|
||||
for level in range(1, level_count):
|
||||
name = f'Sub{level}'
|
||||
cls = type(name, (parent,), {})
|
||||
expected.append((name, level))
|
||||
parent = cls
|
||||
|
||||
result = list(tree(Root))
|
||||
assert len(result) == level_count
|
||||
assert result[0] == ('Root', 0)
|
||||
assert result[-1] == ('Sub99', 99)
|
||||
assert expected == result
|
||||
@@ -1,15 +0,0 @@
|
||||
def tree(cls):
|
||||
yield cls.__name__, 0
|
||||
yield from sub_tree(cls, 1)
|
||||
|
||||
|
||||
def sub_tree(cls, level):
|
||||
for sub_cls in cls.__subclasses__():
|
||||
yield sub_cls.__name__, level
|
||||
yield from sub_tree(sub_cls, level+1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level in tree(BaseException):
|
||||
indent = ' ' * 4 * level
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,90 +0,0 @@
|
||||
from tree import tree
|
||||
|
||||
|
||||
def test_1_level():
|
||||
class One: pass
|
||||
expected = [('One', 0)]
|
||||
result = list(tree(One))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_2_levels_2_leaves():
|
||||
class Branch: pass
|
||||
class Leaf1(Branch): pass
|
||||
class Leaf2(Branch): pass
|
||||
expected = [
|
||||
('Branch', 0),
|
||||
('Leaf1', 1),
|
||||
('Leaf2', 1),
|
||||
]
|
||||
result = list(tree(Branch))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_3_levels_1_leaf():
|
||||
class X: pass
|
||||
class Y(X): pass
|
||||
class Z(Y): pass
|
||||
expected = [
|
||||
('X', 0),
|
||||
('Y', 1),
|
||||
('Z', 2),
|
||||
]
|
||||
result = list(tree(X))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_1_leaf():
|
||||
class Level0: pass
|
||||
class Level1(Level0): pass
|
||||
class Level2(Level1): pass
|
||||
class Level3(Level2): pass
|
||||
expected = [
|
||||
('Level0', 0),
|
||||
('Level1', 1),
|
||||
('Level2', 2),
|
||||
('Level3', 3),
|
||||
]
|
||||
|
||||
result = list(tree(Level0))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_4_levels_3_leaves():
|
||||
class A: pass
|
||||
class B1(A): pass
|
||||
class B2(A): pass
|
||||
class C1(B1): pass
|
||||
class C2(B2): pass
|
||||
class D1(C1): pass
|
||||
class D2(C1): pass
|
||||
expected = [
|
||||
('A', 0),
|
||||
('B1', 1),
|
||||
('C1', 2),
|
||||
('D1', 3),
|
||||
('D2', 3),
|
||||
('B2', 1),
|
||||
('C2', 2),
|
||||
]
|
||||
|
||||
result = list(tree(A))
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_many_levels_1_leaf():
|
||||
class Root: pass
|
||||
level_count = 100
|
||||
expected = [('Root', 0)]
|
||||
parent = Root
|
||||
for level in range(1, level_count):
|
||||
name = f'Sub{level}'
|
||||
cls = type(name, (parent,), {})
|
||||
expected.append((name, level))
|
||||
parent = cls
|
||||
|
||||
result = list(tree(Root))
|
||||
assert len(result) == level_count
|
||||
assert result[0] == ('Root', 0)
|
||||
assert result[-1] == ('Sub99', 99)
|
||||
assert expected == result
|
||||
@@ -1,10 +0,0 @@
|
||||
def tree(cls, level=0):
|
||||
yield cls.__name__, level
|
||||
for sub_cls in cls.__subclasses__():
|
||||
yield from tree(sub_cls, level+1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for cls_name, level in tree(BaseException):
|
||||
indent = ' ' * 4 * level
|
||||
print(f'{indent}{cls_name}')
|
||||
@@ -1,29 +0,0 @@
|
||||
""" Example from `Python: The Full Monty`__ -- A Tested Semantics for the
|
||||
Python Programming Language
|
||||
|
||||
__ http://cs.brown.edu/~sk/Publications/Papers/Published/pmmwplck-python-full-monty/
|
||||
|
||||
"The following program, [...] seems to perform a simple abstraction over the
|
||||
process of yielding:"
|
||||
|
||||
Citation:
|
||||
|
||||
Joe Gibbs Politz, Alejandro Martinez, Matthew Milano, Sumner Warren,
|
||||
Daniel Patterson, Junsong Li, Anand Chitipothu, and Shriram Krishnamurthi.
|
||||
2013. Python: the full monty. SIGPLAN Not. 48, 10 (October 2013), 217-232.
|
||||
DOI=10.1145/2544173.2509536 http://doi.acm.org/10.1145/2544173.2509536
|
||||
"""
|
||||
|
||||
# tag::YIELD_DELEGATE_FAIL[]
|
||||
def f():
|
||||
def do_yield(n):
|
||||
yield n
|
||||
x = 0
|
||||
while True:
|
||||
x += 1
|
||||
do_yield(x)
|
||||
# end::YIELD_DELEGATE_FAIL[]
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Invoking f() results in an infinite loop')
|
||||
f()
|
||||
@@ -1,24 +0,0 @@
|
||||
""" Example adapted from ``yield_delegate_fail.py``
|
||||
|
||||
The following program performs a simple abstraction over the process of
|
||||
yielding.
|
||||
|
||||
"""
|
||||
|
||||
# tag::YIELD_DELEGATE_FIX[]
|
||||
def f():
|
||||
def do_yield(n):
|
||||
yield n
|
||||
x = 0
|
||||
while True:
|
||||
x += 1
|
||||
yield from do_yield(x)
|
||||
# end::YIELD_DELEGATE_FIX[]
|
||||
|
||||
if __name__ == '__main__':
|
||||
print('Invoking f() now produces a generator')
|
||||
g = f()
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
print(next(g))
|
||||
|
||||
Reference in New Issue
Block a user