diff --git a/converter/assets.py b/converter/assets.py index 5667ac8f..29517b04 100644 --- a/converter/assets.py +++ b/converter/assets.py @@ -51,11 +51,13 @@ def _convert_assets(config, generate_dir, pdfs_for_convert, convert_from_path, b dst_folder.mkdir(exist_ok=True, parents=True) try: - pages = convert_from_path(pdf_file, 500) + pages = convert_from_path(pdf_file, 300) if pages: image = Path(pdf.replace('.pdf', '.jpg')) page = pages[0] page.save(dst_folder.joinpath(image.name), 'JPEG') + except KeyboardInterrupt as e: + raise e except BaseException as e: logging.error("convert %s to jpg error" % pdf) logging.error(e) diff --git a/converter/convert.py b/converter/convert.py index 2be0a40c..4790010c 100644 --- a/converter/convert.py +++ b/converter/convert.py @@ -54,14 +54,14 @@ def prepare_codio_rules(config): def cleanup_latex(lines): updated = [] starts = ( - '%', '\\index{', '\\label{', '\\markboth{', '\\addcontentsline{', - '\\vspace', '\\newpage', '\\noindent', - '\\ttfamily', '\\chapter', '\\section', '\\newcommand', '\\vfill', '\\pagebreak' + '%', '\\label{', '\\markboth{', '\\addcontentsline{', + '\\vspace', '\\newpage', '\\vfill', '\\pagebreak', + '\\ttfamily', '\\chapter', '\\section', '\\newcommand' ) for line in lines: if line.startswith(starts): continue - updated.append(line) + updated.append(line.rstrip('\n')) return updated @@ -191,7 +191,7 @@ def prepare_structure(generate_dir): def make_metadata_items(config): book = { - "name": "TODO: book name", + "name": config.get("name"), "children": [] } metadata = { diff --git a/converter/latex2markdown.py b/converter/latex2markdown.py index b44140d1..dce4d151 100644 --- a/converter/latex2markdown.py +++ b/converter/latex2markdown.py @@ -1,6 +1,8 @@ import uuid import re +from converter.markdown.del_icons_description import DelIconsDescription +from converter.markdown.equation import Equation from converter.markdown.inline_code_block import InlineCodeBlock from converter.markdown.code_block import CodeBlock from converter.markdown.bold import Bold @@ -24,6 +26,7 @@ from converter.markdown.cleanup import Cleanup from converter.markdown.exercise import Exercise from converter.markdown.figure import Figure +from converter.markdown.competency import Competency from converter.markdown.refs import Refs from converter.markdown.sidebar import Sidebar from converter.markdown.eqnarray import EqnArray @@ -42,7 +45,10 @@ from converter.markdown.screencast import Screencast from converter.markdown.tabularx import Tabularx from converter.markdown.tabular import Tabular +from converter.markdown.tags import Tags +from converter.markdown.textfigure import Textfigure from converter.markdown.unescape import UnEscape +from converter.markdown.turingwinner import TuringWinner class LaTeX2Markdown(object): @@ -74,6 +80,7 @@ def increment_figure_counter(self, figure_counter): def _latex_to_markdown(self): output = self._latex_string + output = DelIconsDescription(output).convert() output, figure_counter = TableFigure( output, self._caret_token, self._load_workspace_file, self._figure_counter_offset, self._chapter_num, self._refs @@ -86,6 +93,7 @@ def _latex_to_markdown(self): output = Ignore(output).convert() output = SaasSpecific(output, self._caret_token).convert() output = ItalicBold(output).convert() + output = Equation(output, self._caret_token).convert() output, source_codes = CodeBlock( output, self._percent_token, self._caret_token, self._remove_trinket ).convert() @@ -106,10 +114,12 @@ def _latex_to_markdown(self): self._source_codes.extend(source_codes) output = re.sub(r"\\%", self._percent_token, output) output = InlineCodeBlock(output, self._percent_token).convert() + output = Textfigure(output, self._caret_token).convert() # remove comments output = RemoveComments(output).convert() output = Quotation(output, self._caret_token).convert() + output = Competency(output, self._caret_token).convert() output = Paragraph(output).convert_without_tags() output = Refs(output, self._refs).convert() output = Links(output).convert() @@ -123,6 +133,9 @@ def _latex_to_markdown(self): output = PitFall(output, self._caret_token).convert() output = Summary(output, self._caret_token).convert() output = Chips(output, self._caret_token).convert() + output, images = TuringWinner(output, self._caret_token, self._detect_asset_ext,).convert() + if images: + self._pdfs.extend(images) output = Cleanup(output).convert() output, images, figure_counter = PicFigure( @@ -134,7 +147,8 @@ def _latex_to_markdown(self): if images: self._pdfs.extend(images) output, images, figure_counter = Figure( - output, self._figure_counter_offset, self._chapter_num, self._detect_asset_ext, self._caret_token + output, self._figure_counter_offset, self._chapter_num, self._detect_asset_ext, + self._caret_token, self._refs ).convert() if images: self._pdfs.extend(images) @@ -164,8 +178,11 @@ def _latex_to_markdown(self): output = Center(output, self._caret_token).convert() output = UnEscape(output).convert() + output = Tags(output).convert() output = NewLine(output).convert() + output = re.sub(r"\n? *\\label{.*?}", r"", output) + # convert all matched % back output = re.sub(self._percent_token, "%", output) output = re.sub(self._caret_token, "\n", output) diff --git a/converter/markdown/block.py b/converter/markdown/block.py index 1b559f9d..815ea836 100644 --- a/converter/markdown/block.py +++ b/converter/markdown/block.py @@ -69,9 +69,8 @@ def _format_block_contents(self, block_name, block_contents): for line in block_contents.lstrip().rstrip().split("\n"): line = line.lstrip().rstrip() line = line.replace("\\\\", "
") - indented_line = line_indent_char + line + self._caret_token - output_str += indented_line - return output_str + output_str += f'{line} ' + return line_indent_char + output_str def _format_block_name(self, block_name, block_title=None): block_config = self._block_configuration[block_name] diff --git a/converter/markdown/block_matcher.py b/converter/markdown/block_matcher.py index 7e32791f..f5b429b0 100644 --- a/converter/markdown/block_matcher.py +++ b/converter/markdown/block_matcher.py @@ -4,14 +4,15 @@ def match_block(chars, output, repl_func): level = 0 if '{' in chars else 1 for index in range(pos + len(chars), len(output), 1): ch = output[index] - if ch == '}': + prev_char = output[index - 1] + if ch == '}' and prev_char != '\\': if level == 0: start_position = pos+len(chars) if '{' in chars else output.find("{", pos) + 1 output = output[0:pos] + repl_func(output[start_position:index]) + output[index + 1:] break else: level += 1 - elif ch == '{': + elif ch == '{' and prev_char != '\\': level -= 1 pos = output.find(chars) return output diff --git a/converter/markdown/bold.py b/converter/markdown/bold.py index 6004477e..f0ac8615 100644 --- a/converter/markdown/bold.py +++ b/converter/markdown/bold.py @@ -9,12 +9,12 @@ def __init__(self, latex_str): def convert(self): output = self.str - output = re.sub(r"\\textbf{(.*?)}", r"**\1**", output, flags=re.DOTALL + re.VERBOSE) - output = re.sub(r"{\\bf[ ](.*?)}", r"**\1**", output, flags=re.DOTALL + re.VERBOSE) - output = re.sub(r"{\\sf[ ](.*?)}", r"**\1**", output, flags=re.DOTALL + re.VERBOSE) + output = re.sub(r"\\textbf{(.*?)}", r"\1", output, flags=re.DOTALL + re.VERBOSE) + output = re.sub(r"{\\bf[ ](.*?)}", r"\1", output, flags=re.DOTALL + re.VERBOSE) + output = re.sub(r"{\\sf[ ](.*?)}", r"\1", output, flags=re.DOTALL + re.VERBOSE) - output = match_block("\\B{", output, lambda match: f"**{match}**") - output = match_block("\\C{", output, lambda match: f"**{match}**") - output = match_block("\\T{", output, lambda match: f"**{match}**") + output = match_block("\\B{", output, lambda match: f"{match}") + output = match_block("\\C{", output, lambda match: f"{match}") + output = match_block("\\T{", output, lambda match: f"{match}") return output diff --git a/converter/markdown/center.py b/converter/markdown/center.py index 046f6d5c..bfa4d37f 100644 --- a/converter/markdown/center.py +++ b/converter/markdown/center.py @@ -2,7 +2,8 @@ from converter.markdown.text_as_paragraph import TextAsParagraph -center_re = re.compile(r"""\\begin{center}(?P.*?)\\end{center}""", flags=re.DOTALL + re.VERBOSE) +center_re = re.compile(r"""\\begin{(center|centering)}(?P.*?)\\end{(center|centering)}""", + flags=re.DOTALL + re.VERBOSE) class Center(TextAsParagraph): diff --git a/converter/markdown/checkyourself.py b/converter/markdown/checkyourself.py index d54b9811..468509ed 100644 --- a/converter/markdown/checkyourself.py +++ b/converter/markdown/checkyourself.py @@ -17,13 +17,20 @@ def make_answer_block(self, matchobj): answer_block_contents = matchobj.group('answer_block_contents') answer_block_contents = answer_block_contents.replace("\\\\", "
") answer_block_contents = self.to_paragraph(answer_block_contents) - return '
Check yourself{}
'.format(answer_block_contents) + caret_token = self._caret_token + return f'{caret_token}

Check yourself' \ + f'{caret_token}{caret_token}{answer_block_contents}

' def make_block(self, matchobj): block_contents = matchobj.group('block_contents') + block_contents = re.sub(r"\s*\n\s*", " ", block_contents).strip() + block_contents = block_contents.replace("\\\\", "
") + block_contents = re.sub(r"\n? *\\label{.*?} *", r"", block_contents) answer_str = answer_re.sub(self.make_answer_block, block_contents) caret_token = self._caret_token - return f'{caret_token}|||challenge{caret_token}{answer_str}{caret_token}|||{caret_token}' + answer_str = re.sub(r"\\{", r"{", answer_str) + answer_str = re.sub(r"\\}", r"}", answer_str) + return f'{caret_token}|||challenge{caret_token}{answer_str}{caret_token}{caret_token}|||{caret_token}' def convert(self): return checkyourself_re.sub(self.make_block, self.str) diff --git a/converter/markdown/chips.py b/converter/markdown/chips.py index eb1c370c..6a7affee 100644 --- a/converter/markdown/chips.py +++ b/converter/markdown/chips.py @@ -2,8 +2,8 @@ from converter.markdown.text_as_paragraph import TextAsParagraph -chips_re = re.compile(r"""\\begin{chips}{(?P.*?)}(?P<block_contents>.*?)\\end{chips}""", - flags=re.DOTALL + re.VERBOSE) +chips_re = re.compile(r"""\\begin{chips}{(?P<title>.*?)}({(?P<ref>.*?)})?(?P<block_contents>.*?)\\end{chips}""", + flags=re.DOTALL) class Chips(TextAsParagraph): @@ -11,12 +11,15 @@ def __init__(self, latex_str, caret_token): super().__init__(latex_str, caret_token) def make_block(self, matchobj): + caret_token = self._caret_token block_contents = matchobj.group('block_contents') block_contents = self.to_paragraph(block_contents) title = matchobj.group('title') title = self.to_paragraph(title) - caret_token = self._caret_token - return f'## {title}{caret_token}{block_contents}{caret_token}' + ref = matchobj.group('ref') + if ref is not None: + return f'## {title}{caret_token}{ref}{caret_token}{caret_token}{block_contents}{caret_token}' + return f'## {title}{caret_token}{caret_token}{block_contents}{caret_token}' def convert(self): return chips_re.sub(self.make_block, self.str) diff --git a/converter/markdown/cite.py b/converter/markdown/cite.py index c5ab0c25..991e2962 100644 --- a/converter/markdown/cite.py +++ b/converter/markdown/cite.py @@ -4,11 +4,21 @@ from converter.markdown.block_matcher import match_block from converter.guides.tools import get_text_in_brackets -cite_re = re.compile(r"""~?\\cite{(?P<ref>.*?)}""", flags=re.DOTALL + re.VERBOSE) +cite_re = re.compile(r"""\\cite{(?P<ref>.*?)}""", flags=re.DOTALL + re.VERBOSE) bib_re = re.compile(r"""@(?P<type>.*?){(?P<ref>.*?),""", flags=re.DOTALL + re.VERBOSE) +def clean_text(string): + string = string.replace("{", "").replace("}", "") + string = string.replace("\\&", "&") + string = string.replace("`", "'") + string = string.replace("---", "-") + string = string.replace("\\", "") + string = string.replace("o{}", "o") + return string + + class Cite(object): _bib_file = None _bib_entries = [] @@ -34,13 +44,13 @@ def __init__(self, latex_str, load_workspace_file): def make_bib_content(line): bib_entry = {} - for item in line.split(',\n'): + for item in re.split(r',\s*\n', line): item = item.strip() sub_items = item.split('=', 1) if len(sub_items) > 1: value = sub_items[1].strip() key = sub_items[0].strip().lower() - if value.startswith('{'): + if value.startswith('{') or value.startswith('"{') or value.startswith('\'{'): value = get_text_in_brackets(value) value = ' '.join(value.split('\n')) bib_entry[key] = value @@ -65,13 +75,13 @@ def get_bib_text(self, ref): bib_item = Cite._bib_file.get(ref.lower(), {}) if bib_item: if bib_item.get('title') and bib_item.get('author'): - author = bib_item.get('author') - title = bib_item.get('title') + author = clean_text(bib_item.get('author')) + title = clean_text(bib_item.get('title')) return f'<abbr title="{title}">{author}</abbr>' elif bib_item.get('title'): - return bib_item.get('title') + return clean_text(bib_item.get('title')) elif bib_item.get('author'): - return bib_item.get('author') + return clean_text(bib_item.get('author')) return ref def make_block(self, matchobj): diff --git a/converter/markdown/cleanup.py b/converter/markdown/cleanup.py index 873a62ff..d5f0fef3 100644 --- a/converter/markdown/cleanup.py +++ b/converter/markdown/cleanup.py @@ -18,8 +18,8 @@ def convert(self): output = re.sub(r"\\'{(.*?)}", r"\1́", output) - output = re.sub(r"(.*?)(~)(.*?)", r"\1 \3", output) - output = re.sub(r"(~)(.*?)", r" \2", output) - output = re.sub(r"(.*?)(~)", r"\1 ", output) + output = re.sub(r"(.*?)(?:(?<!\\)(~))(.*?)", r"\1 \3", output) + output = re.sub(r"(?:(?<!\\)(~))(.*?)", r" \2", output) + output = re.sub(r"(.*?)(?:(?<!\\)(~))", r"\1 ", output) return output diff --git a/converter/markdown/codefilefigure.py b/converter/markdown/codefilefigure.py index 730b2453..adcb0d76 100644 --- a/converter/markdown/codefilefigure.py +++ b/converter/markdown/codefilefigure.py @@ -51,7 +51,7 @@ def make_block(self, matchobj): self._refs.get(label).get('ref') ) - return f'{caret_token}{caption}{caret_token}**source:{file_path}**{caret_token}' \ + return f'{caret_token}{caption}{caret_token}**source: {file_path}**{caret_token}' \ f'```code{caret_token}{file_content}{caret_token}```{caret_token}{replace_token}' def remove_matched_token(self, output, chars): diff --git a/converter/markdown/competency.py b/converter/markdown/competency.py new file mode 100644 index 00000000..4c74ff8b --- /dev/null +++ b/converter/markdown/competency.py @@ -0,0 +1,21 @@ +import re + +from converter.markdown.block_matcher import match_block +from converter.markdown.text_as_paragraph import TextAsParagraph + +competency_re = re.compile(r"""\\competency(\[(.*?)\])?{(?P<block_contents>.*?)}""", + flags=re.DOTALL + re.VERBOSE) + +class Competency(TextAsParagraph): + def __init__(self, latex_str, caret_token): + super().__init__(latex_str, caret_token) + + def make_block(self, matchobj): + block_contents = matchobj.group('block_contents') + block_contents = re.sub(r"\s*\n\s*", " ", block_contents).strip() + block_contents = block_contents.replace("\\\\", "<br/>") + caret_token = self._caret_token + return f'{caret_token}|||topic{caret_token}## Competency{caret_token}{block_contents}{caret_token}{caret_token}|||{caret_token}' + + def convert(self): + return competency_re.sub(self.make_block, self.str) \ No newline at end of file diff --git a/converter/markdown/del_icons_description.py b/converter/markdown/del_icons_description.py new file mode 100644 index 00000000..55dbae5f --- /dev/null +++ b/converter/markdown/del_icons_description.py @@ -0,0 +1,14 @@ +import re + + +class DelIconsDescription(object): + def __init__(self, latex_str): + self.str = latex_str + + def convert(self): + output = self.str + + output = re.sub(r"\s*We also use.*?look them up.\s*", "", output) + output = re.sub(r"\\tablefigure{ch_intro/tables/icons_table}{.*?}{.*?}", "", output) + + return output diff --git a/converter/markdown/elaboration.py b/converter/markdown/elaboration.py index e1d92bb2..7d604ad0 100644 --- a/converter/markdown/elaboration.py +++ b/converter/markdown/elaboration.py @@ -2,7 +2,7 @@ from converter.markdown.text_as_paragraph import TextAsParagraph -elaboration_re = re.compile(r"""\\begin{elaboration}{(?P<title>.*?)}(?P<block_contents>.*?)\\end{elaboration}""", +elaboration_re = re.compile(r"""(\s+)?\\begin{elaboration}{(?P<title>.*?)}(?P<block_contents>.*?)\\end{elaboration}""", flags=re.DOTALL + re.VERBOSE) @@ -16,7 +16,7 @@ def make_block(self, matchobj): title = self.to_paragraph(title) block_contents = self.to_paragraph(block_contents) caret_token = self._caret_token - return f'## {title}{caret_token}{block_contents}' + return f'{caret_token}## {title}{caret_token}{block_contents}{caret_token}' def convert(self): return elaboration_re.sub(self.make_block, self.str) diff --git a/converter/markdown/equation.py b/converter/markdown/equation.py new file mode 100644 index 00000000..648fba4d --- /dev/null +++ b/converter/markdown/equation.py @@ -0,0 +1,19 @@ +import re + +equation_re = re.compile(r"""\\begin{equation}(?P<block_contents>.*?)\\end{equation}""", flags=re.DOTALL) + + +class Equation(object): + def __init__(self, latex_str, caret_token): + self._caret_token = caret_token + self.str = latex_str + + def make_block(self, matchobj): + block_contents = matchobj.group('block_contents') + block_contents = block_contents.strip() + caret_token = self._caret_token + return f'{caret_token}<center>{caret_token}$${caret_token}{block_contents}' \ + f'{caret_token}$${caret_token}</center>{caret_token}' + + def convert(self): + return equation_re.sub(self.make_block, self.str) diff --git a/converter/markdown/fallacy.py b/converter/markdown/fallacy.py index a604fe72..6ef567e3 100644 --- a/converter/markdown/fallacy.py +++ b/converter/markdown/fallacy.py @@ -13,10 +13,11 @@ def __init__(self, latex_str, caret_token): def make_block(self, matchobj): block_contents = matchobj.group('block_contents') block_contents = self.to_paragraph(block_contents) + block_contents = block_contents.replace('\n', ' ') title = matchobj.group('title') title = self.to_paragraph(title) caret_token = self._caret_token - return f'## {title}{caret_token}{block_contents}' + return f'{caret_token}## {title}{caret_token}{block_contents}' def convert(self): return fallacy_re.sub(self.make_block, self.str) diff --git a/converter/markdown/figure.py b/converter/markdown/figure.py index 13211040..5a28c2b9 100644 --- a/converter/markdown/figure.py +++ b/converter/markdown/figure.py @@ -5,7 +5,7 @@ class Figure(TextAsParagraph): - def __init__(self, latex_str, figure_num, chapter_num, detect_asset_ext, caret_token): + def __init__(self, latex_str, figure_num, chapter_num, detect_asset_ext, caret_token, refs): super().__init__(latex_str, caret_token) self._figure_counter = 0 @@ -13,6 +13,7 @@ def __init__(self, latex_str, figure_num, chapter_num, detect_asset_ext, caret_t self._chapter_num = chapter_num self._pdfs = [] self._detect_asset_ext = detect_asset_ext + self._refs = refs self._figure_re = re.compile(r"""\\begin{figure}(.*?)\n (?P<block_contents>.*?) @@ -20,6 +21,12 @@ def __init__(self, latex_str, figure_num, chapter_num, detect_asset_ext, caret_t def _figure_block(self, matchobj): block_contents = matchobj.group('block_contents') + match_label = re.search(r'\\label{(.*?)}', block_contents) + label = None + if match_label: + label = match_label.group(1) + block_contents = re.sub(r'\\label{.*?}', '', block_contents) + self._figure_counter += 1 images = [] @@ -27,6 +34,11 @@ def _figure_block(self, matchobj): self._chapter_num, self._figure_counter + self._figure_counter_offset ) + if label and self._refs.get(label, {}): + caption = '**<p style="font-size: 10px">Figure {}'.format( + self._refs.get(label).get('ref') + ) + caption_line = None for line in block_contents.strip().split("\n"): @@ -34,7 +46,7 @@ def _figure_block(self, matchobj): if not line.startswith("\\includegraphics"): line = get_text_in_brackets(line) images.append(get_text_in_brackets(line)) - elif line.startswith("\\caption"): + elif "\\caption" in line: if '}' in line: caption += get_text_in_brackets(line) else: @@ -59,9 +71,15 @@ def _figure_block(self, matchobj): markdown_images.append( "![{}]({})".format(caption, image) ) - caret_token = self._caret_token - return f'{self._caret_token.join(markdown_images)}{caret_token}{caret_token}**{caption}**' + caption = caption.replace("**", "").strip() + + if markdown_images: + return f'{self._caret_token.join(markdown_images)}{caret_token}{caret_token}**{caption}**' + + block_contents = re.sub(r"\\caption{(.*?)}", r"", block_contents, flags=re.DOTALL + re.VERBOSE) + + return f'{block_contents}{caret_token}**<p style="font-size: 10px">{caption}</p>**{caret_token}' def convert(self): output = self.str diff --git a/converter/markdown/ignore.py b/converter/markdown/ignore.py index 2144efa5..26e50872 100644 --- a/converter/markdown/ignore.py +++ b/converter/markdown/ignore.py @@ -37,10 +37,29 @@ def make_block(self, group): def convert(self): output = self.str - output = self.remove_chars(output, "\\index{") - output = self.remove_chars(output, "\\label{") + output = re.sub(r"\\index\n{(.*?)}", r"\\index{\1}", output) output = self.remove_chars(output, "\\vspace{") + output = self.remove_chars(output, "\\index{") + output = re.sub(r"\\textit{.*?}", "", output) output = re.sub(r"\\noindent", "", output) + output = re.sub(r"\\bigconcepts", "", output) + output = re.sub(r"\\prereqs?", "", output) + output = re.sub(r"\\relax", "", output) + output = re.sub(r"\\vfill", "", output) + output = re.sub(r"\\indent", "", output) + output = re.sub(r"\\pitfallicon", "", output) + output = re.sub(r"\\fallaciesandpitfalls", "", output) + output = re.sub(r"\\makebox\[.*?\]{}", "<br/>", output) + output = re.sub(r"\\hspace{.*?}", "", output) + output = re.sub(r"\s\\n\n", "", output) + output = re.sub(r"\\fbox{(.*?\\end{minipage}\n)}\n", r"\1", output, flags=re.DOTALL) + output = re.sub(r"\\begin{minipage}{.*?}", "", output) + output = re.sub(r"\\end{minipage}", "", output) + output = re.sub(r"\\newcommand{.*?}{.*?}", "", output) + output = re.sub(r"\\ifhtmloutput.*?(\\hfill\\begin{tabular}{\|.*?\|}).*?\\fi", r"\1", output, flags=re.DOTALL) + output = re.sub(r"\\ifhtmloutput%.*?(\\end{tabular}).*?\\fi", r"\1", output, flags=re.DOTALL) + output = re.sub(r"\\ifhtmloutput.*?(\\begin{tabular}.*?)\\else(.*?)\\fi", r"\2", output, flags=re.DOTALL) + output = re.sub(r"\\ifmobioutput.*?(\\begin{tabular}.*?)\\else(.*?)\\fi", r"\2", output, flags=re.DOTALL) output = ifhtml_re.sub(self.make_block, output) output = ifmobile_re.sub(self.make_block, output) diff --git a/converter/markdown/italic.py b/converter/markdown/italic.py index 4c019e3c..3ac98af5 100644 --- a/converter/markdown/italic.py +++ b/converter/markdown/italic.py @@ -1,5 +1,7 @@ import re +from converter.markdown.block_matcher import match_block + class Italic(object): def __init__(self, latex_str): @@ -7,8 +9,8 @@ def __init__(self, latex_str): def convert(self): output = self.str - output = re.sub(r"\\emph{(.*?)}", r"*\1*", output, flags=re.DOTALL + re.VERBOSE) - output = re.sub(r"{\\em[ ](.*?)}", r"*\1*", output, flags=re.DOTALL + re.VERBOSE) - output = re.sub(r"{\\it[ ](.*?)}", r"*\1*", output, flags=re.DOTALL + re.VERBOSE) + output = match_block("\\emph{", output, lambda match: f"<i>{match}</i>") + output = re.sub(r"{\\em[ ](.*?)}", r"<i>\1</i>", output, flags=re.DOTALL) + output = re.sub(r"{\\it[ ](.*?)}", r"<i>\1</i>", output, flags=re.DOTALL) return output diff --git a/converter/markdown/italic_bold.py b/converter/markdown/italic_bold.py index 60396676..cc8e370b 100644 --- a/converter/markdown/italic_bold.py +++ b/converter/markdown/italic_bold.py @@ -8,7 +8,7 @@ def __init__(self, latex_str): def make_block(self, matchobj): block_contents = matchobj.group('block_contents') block_contents = re.sub(r"\\\\", r"\\", block_contents) - return "_**{}**_".format(block_contents) + return "___{}___".format(block_contents) def convert(self): output = self.str diff --git a/converter/markdown/links.py b/converter/markdown/links.py index 92b07d8f..7fd996bd 100644 --- a/converter/markdown/links.py +++ b/converter/markdown/links.py @@ -1,6 +1,13 @@ import re +def _clean_block(matchobj): + block_link = matchobj.group(1) + block_name = matchobj.group(2) + block_link = re.sub(r"[~]", "", block_link) + return f"[{block_name}]({block_link})" + + class Links(object): def __init__(self, latex_str): self.str = latex_str @@ -8,10 +15,9 @@ def __init__(self, latex_str): def convert(self): output = self.str - output = re.sub(r"\\weblink{(.*?)}([\s%])?{(.*?)}", r"[\3](\1)", output, flags=re.DOTALL + re.VERBOSE) - output = re.sub(r"\\weblink{(.*?)}", r"[\1](\1)", output, flags=re.MULTILINE) + output = re.sub(r"\\weblink{(.*?)}\s*?{((.*?\n*.*?)*)}", _clean_block, output) + output = re.sub(r"\\weblink{(.*?)}", r"[\1](\1)", output) output = re.sub(r"\\url{(.*?)}", r"[\1](\1)", output) - output = re.sub(r"\\href{(.*?)}{(\\[a-z]+)?\s?(.*?)}", r"[\1](\3)", output) - output = re.sub(r"\\href{(.*?)}`(\\[a-z]+)?\s?(.*?)`", r"[\1](\3)", output) + output = re.sub(r"\\href{(.*?)}{(\\[a-z]+)?\s?(.*?)}", r"[\3](\1)", output) return output diff --git a/converter/markdown/lists.py b/converter/markdown/lists.py index 7f89a540..962ee9d0 100644 --- a/converter/markdown/lists.py +++ b/converter/markdown/lists.py @@ -10,6 +10,7 @@ def __init__(self, latex_str, caret_token): self._block_counter = defaultdict(lambda: 1) self._lists_re = re.compile(r"""\\begin{(?P<block_name>enumerate|itemize|description)} # list name + (\s\\addtocounter{.*?}{(?P<start_number>\d)})? # list start number (\[.*?\])? # Optional enumerate settings i.e. (a) (?P<block_contents>.*?) # Non-greedy list contents \\end{(?P=block_name)}""", # closing list @@ -38,33 +39,41 @@ def __init__(self, latex_str, caret_token): } } - def _format_list_contents(self, block_name, block_contents): + def _format_list_contents(self, block_name, block_contents, start_number): block_config = self._block_configuration[block_name] - list_heading = block_config["list_heading"] + enum = False + + if block_name == 'enumerate': + enum = True + if start_number is not None: + start_number = int(start_number) + else: + start_number = 0 output_str = "" - for line in block_contents.lstrip().rstrip().split("\n"): + for line in block_contents.lstrip().rstrip().split("\\item"): line = line.lstrip().rstrip() line = line.replace("\\\\", "<br/>") - - markdown_list_line = re.sub(r"\\item(\s+)?", list_heading, line) + line = re.sub(r" {4,}", " ", line) if not line: continue - if "\\term" in line: - if output_str: - output_str = output_str.strip() + self._caret_token - markdown_list_line = markdown_list_line.replace("\\term", list_heading) - if "\\term{" in line: - markdown_list_line = markdown_list_line.replace("{", "**", 1) - markdown_list_line = markdown_list_line.replace("}", "**", 1) - elif "\\item" in line: - if output_str: - output_str = output_str.strip() + self._caret_token - if "\\item[" in line: - markdown_list_line = markdown_list_line.replace("[", "**", 1) - markdown_list_line = markdown_list_line.replace("]", "**", 1) - output_str += markdown_list_line + ' ' + + if enum: + start_number += 1 + list_heading = f'{start_number}. ' + + cline = "" + for sub_str in line.split("\n"): + if not sub_str: + continue + cline += sub_str.strip() + " " + + md_list_line = list_heading + cline.strip() + self._caret_token + if cline.startswith('['): + md_list_line = md_list_line.replace("[", "**", 1) + md_list_line = md_list_line.replace("]", "**", 1) + output_str += md_list_line return output_str def _format_block_name(self, block_name, block_title=None): @@ -91,18 +100,19 @@ def _format_block_name(self, block_name, block_title=None): def _replace_block(self, matchobj): block_name = matchobj.group('block_name') block_contents = matchobj.group('block_contents') + start_number_str = matchobj.group('start_number') block_title = matchobj.groupdict().get('block_title') if '\\begin{enumerate' in block_contents or '\\begin{itemize' in block_contents: block_contents = self._lists_re.sub(self._replace_block, block_contents) - formatted_contents = self._format_list_contents(block_name, block_contents) + formatted_contents = self._format_list_contents(block_name, block_contents, start_number_str) formatted_contents = self.to_paragraph(formatted_contents) header = self._format_block_name(block_name, block_title) caret_token = self._caret_token - output_str = f"{header}{caret_token}{caret_token}{formatted_contents}{caret_token}{caret_token}" + output_str = f"{header}{caret_token}{caret_token}{formatted_contents}{caret_token}" if block_name == "description": output_str = f"{header}{formatted_contents}{caret_token}{caret_token}" return output_str diff --git a/converter/markdown/match_elements.py b/converter/markdown/match_elements.py new file mode 100644 index 00000000..f8b02a4d --- /dev/null +++ b/converter/markdown/match_elements.py @@ -0,0 +1,21 @@ +def match_elements(text, n_matches): + level = 0 + offset = 0 + found_matches = 0 + founds = [] + last_index = 0 + for index in range(0, len(text), 1): + ch = text[index] + last_index = index + if ch == '}': + level -= 1 + if level == 0: + start_position = text.find("{", offset) + 1 + offset += start_position + founds.append(text[start_position:index]) + found_matches += 1 + if found_matches == n_matches: + break + elif ch == '{': + level += 1 + return founds, last_index diff --git a/converter/markdown/newline.py b/converter/markdown/newline.py index 466d4de0..bbd232c7 100644 --- a/converter/markdown/newline.py +++ b/converter/markdown/newline.py @@ -8,7 +8,6 @@ def __init__(self, latex_str): def convert(self): output = self.str output = re.sub(r"^\\\\ ", "<br/>", output, flags=re.MULTILINE) - output = re.sub(r"\\newline ", "<br/>", output, flags=re.MULTILINE) - output = re.sub(r"\\newline$", "<br/>", output, flags=re.MULTILINE) + output = re.sub(r"\\newline", "<br/>", output, flags=re.MULTILINE) return output diff --git a/converter/markdown/picfigure.py b/converter/markdown/picfigure.py index e37ab539..42f8b7cc 100644 --- a/converter/markdown/picfigure.py +++ b/converter/markdown/picfigure.py @@ -1,10 +1,6 @@ -import re - +from converter.markdown.match_elements import match_elements from converter.markdown.text_as_paragraph import TextAsParagraph -picfigure_re = re.compile(r"""\\picfigure{(?P<image>.*?)}{(?P<refs>.*?)}{(?P<content>.*?)}""", - flags=re.DOTALL + re.VERBOSE) - class PicFigure(TextAsParagraph): def __init__(self, latex_str, caret_token, detect_asset_ext, figure_counter_offset, chapter_num, refs): @@ -16,10 +12,8 @@ def __init__(self, latex_str, caret_token, detect_asset_ext, figure_counter_offs self._chapter_num = chapter_num self._refs = refs - def make_block(self, matchobj): - content = matchobj.group('content').strip() - label = matchobj.group('refs').strip() - image = matchobj.group('image').strip() + def make_block(self, image, label, content): + content = content.strip() if '.' not in image: ext = self._detect_asset_ext(image) if ext: @@ -28,17 +22,29 @@ def make_block(self, matchobj): self.images.append(image) image = image.replace('.pdf', '.jpg') self._figure_counter += 1 - caption = '**Figure {}.{}**'.format( + caption = '**<p style="font-size: 10px">Figure {}.{}'.format( self._chapter_num, self._figure_counter + self._figure_counter_offset ) if self._refs.get(label, {}): - caption = '**Figure {}**'.format( + caption = '**<p style="font-size: 10px">Figure {}'.format( self._refs.get(label).get('ref') ) caret_token = self._caret_token - return f"![{content}]({image}){caret_token}{caption}{caret_token}{content}" + return f"![{content}]({image}){caret_token}{caption}: {content}</p>**{caret_token}" def convert(self): self.images.clear() pdfs = filter(lambda img: img.endswith('.pdf'), self.images) - return picfigure_re.sub(self.make_block, self.str), list(pdfs), self._figure_counter + out = self.str + search_str = "\\picfigure" + pos = out.find(search_str) + while pos != -1: + matches, index = match_elements(out[pos + len(search_str):], 3) + start = out[0:pos] + end_pos = pos + len(search_str) + 1 + index + end = out[end_pos:] + out = start + self.make_block( + matches[0], matches[1], matches[2] + ) + end + pos = out.find(search_str, end_pos + 1) + return out, list(pdfs), self._figure_counter diff --git a/converter/markdown/pitfall.py b/converter/markdown/pitfall.py index 2283f764..bbcb8069 100644 --- a/converter/markdown/pitfall.py +++ b/converter/markdown/pitfall.py @@ -13,6 +13,7 @@ def __init__(self, latex_str, caret_token): def make_block(self, matchobj): block_contents = matchobj.group('block_contents') block_contents = self.to_paragraph(block_contents) + block_contents = block_contents.replace('\n', ' ') title = matchobj.group('title') title = self.to_paragraph(title) caret_token = self._caret_token diff --git a/converter/markdown/quotation.py b/converter/markdown/quotation.py index 4baa00c5..b5b664e8 100644 --- a/converter/markdown/quotation.py +++ b/converter/markdown/quotation.py @@ -1,5 +1,6 @@ import re +from converter.markdown.match_elements import match_elements from converter.markdown.text_as_paragraph import TextAsParagraph @@ -7,20 +8,19 @@ class Quotation(TextAsParagraph): def __init__(self, latex_str, caret_token): super().__init__(latex_str, caret_token) - self._makequotation_re = re.compile(r"""\\makequotation{(?P<block_contents>.*?)}([\s]+)? - {(?P<block_author>.*?)}([ \t]+)?$""", - flags=re.DOTALL + re.VERBOSE + re.MULTILINE) - - def _makequotation_block(self, matchobj): - block_contents = matchobj.group('block_contents') - block_author = matchobj.group('block_author') - block_contents = self.to_paragraph(block_contents) - block_contents = re.sub(r"\\\\", '<br/>', block_contents) - caret_token = self._caret_token - return f'> {block_contents}{caret_token}>{caret_token}> __{block_author}__{caret_token}{caret_token}' - def convert(self): - output = self.str - output = self._makequotation_re.sub(self._makequotation_block, output) - - return output + out = self.str + search_str = "\\makequotation" + pos = out.find(search_str) + caret_token = self._caret_token + while pos != -1: + matches, index = match_elements(out[pos + len(search_str):], 2) + start = out[0:pos] + end_pos = pos + len(search_str) + 1 + index + end = out[end_pos:] + matches[0] = re.sub(r"\\\\", "<br/>", matches[0]) + matches[1] = matches[1].strip('~') + out = start + f'{caret_token}> {matches[0]}{caret_token}>' \ + f'{caret_token}> __{matches[1]}__{caret_token}{caret_token}' + end + pos = out.find(search_str, end_pos + 1) + return out diff --git a/converter/markdown/refs.py b/converter/markdown/refs.py index fda627f5..9f26d486 100644 --- a/converter/markdown/refs.py +++ b/converter/markdown/refs.py @@ -12,8 +12,13 @@ def __init__(self, latex_str, refs): def _refs_block(self, matchobj): ref_name = matchobj.group('ref_name') - refs = self._refs.get(ref_name, {'ref': ref_name}) - return '{}'.format(refs.get('ref', '')) + sec_match = re.search(r'sec:.*?:(.*)', ref_name) + if sec_match: + ref_name = sec_match.group(1) + ref = self._refs.get(ref_name, {'ref': ref_name}) + if ref.get('item_num') is not None: + return '{}'.format(ref.get('item_num', '')) + return '{}'.format(ref.get('ref', '')) def _page_refs_block(self, matchobj): ref_name = matchobj.group('ref_name') diff --git a/converter/markdown/saas_specific.py b/converter/markdown/saas_specific.py index bc85a5db..1de895c5 100644 --- a/converter/markdown/saas_specific.py +++ b/converter/markdown/saas_specific.py @@ -9,7 +9,7 @@ def __init__(self, latex_str, caret_token): self.str = latex_str self._saas_icons_re = re.compile(r"""\\(dry|reuse|codegen|concise|coc|legacy|beauty|tool| - learnbydoing|automation|curric|idio|lookout)(\s+)?(\[.*?\])?({.*\})?""", + learnbydoing|automation|curric|idio|lookout)(\s+)?(\[.*?\])?({.*?\})?""", flags=re.DOTALL + re.VERBOSE) self._saas_2icons_re = re.compile( @@ -36,6 +36,8 @@ def convert(self): output = re.sub(r"\\putbib", "", output) output = re.sub(r"\\ig({\})?", "Instructors' Manual", output) output = re.sub(r"\\js({\})?", "JavaScript", output) + output = re.sub(r"\\tl({\})?", r"\\tl", output) + output = re.sub(r"\\tg({\})?", r"\\tg", output) output = re.sub(r"\\slash({\})?", "/", output) output = re.sub(r"\\ldots({\})?", r"...", output) output = re.sub(r"\\LaTeX({\})?", r"LaTeX", output) @@ -43,11 +45,37 @@ def convert(self): output = re.sub(r"\\\"{(.*?)\}", r"\1", output) output = re.sub(r"\\spaceship({\})?", r"<=>", output) output = re.sub(r"\\thinspace({\})?", r" ", output) - output = re.sub(r"\\tl({\})?", r"<", output) - output = re.sub(r"\\tg({\})?", r">", output) - output = re.sub(r"\\ttil({\})?", r"~", output) + output = re.sub(r"\\ttil({\})?", r"\\~", output) output = re.sub(r"\\textbar({\})?", r"|", output) output = re.sub(r"\\hrule", "<hr>", output) + output = re.sub(r"\\hfill", "", output) + output = re.sub(r"(?:{\\small|\\small{)(?:(.*?)^\s*}(?=\s*\n))", r"\1", output, flags=re.DOTALL + re.MULTILINE) + output = re.sub(r"\\raggedright{(.*?(?=^\}$))}", r"\1", output, flags=re.DOTALL + re.MULTILINE) + output = re.sub(r"\\Beta{.*?}", "", output, flags=re.DOTALL) + output = re.sub(r"\\times{}", r"\\times", output) + output = re.sub(r"\\cline{.*?}", "", output) + output = re.sub(r"\\bs{}", r"\\", output) + output = re.sub(r"==", r"\\=\\=", output) + output = re.sub(r"\\o{}", r"o", output) + output = re.sub(r"\\v{s}", r"vs", output) + output = re.sub(r"\\c{(.*?)}", r"\1", output) + output = re.sub(r"\.\\ ", ". ", output) + output = re.sub(r"\.\\\\\*\}", ".}", output) + output = re.sub(r"\\/", "", output) + output = re.sub(r"\\_\\-", "_", output) + output = re.sub(r"\\-\\_", "_", output) + output = re.sub(r"\.\\-", ".", output) + output = re.sub(r"\\#\\-", "\\#", output) + output = re.sub(r"\((?:(?![s])([a-z]))\)", r"( \1 )", output) + output = re.sub(r"__~to~__", r" __to__ ", output) + output = re.sub(r"-{}-", r"- - ", output) + output = re.sub(r"\[\]\((.*?)\)", r"[\\](\1)", output) + output = re.sub(r"{(name_with_rating)}", r"<b>\1</b>", output) + output = re.sub(r"\\textsection", "$", output) + output = re.sub(r"\\fillinblank{}", "_________", output) + output = re.sub(r"\\textasciicircum({})?", r"\^", output) + output = re.sub(r"\\textbackslash{}", r"\\", output) + output = re.sub(r"\\end{(.*?)}\s", r"\\end{\1}\n", output) output = self._saas_icons_re.sub(self._saas_icons_block, output) output = self._saas_2icons_re.sub(self._saas_icons_block, output) output = self._new_re.sub(self.make_block, output) diff --git a/converter/markdown/sidebar.py b/converter/markdown/sidebar.py index 393d5597..644d7b98 100644 --- a/converter/markdown/sidebar.py +++ b/converter/markdown/sidebar.py @@ -11,36 +11,28 @@ def __init__(self, latex_str, detect_asset_ext, caret_token): self._pdfs = [] - self._sidebargraphic_re = re.compile(r"""\\begin{sidebargraphic}(\[(?P<props>.*?)])?{(?P<block_graphics>.*?)} - {(?P<block_name>.*?)} + self._sidebargraphic_re = re.compile(r"""\\begin{sidebargraphic}(\[(?P<props>.*?)])? + {(?P<block_graphics>.*?)}(?:%?[]*\n[ ]*)? + (?:{(?P<block_name>.*?)})? (?P<block_contents>.*?) \\end{sidebargraphic}""", flags=re.DOTALL + re.VERBOSE) - self._sidebar_re = re.compile(r"""\\begin{sidebar} - (?P<block_contents>.*?) - \\end{sidebar}""", flags=re.DOTALL + re.VERBOSE) + self._sidebar_re = re.compile(r"""\\begin{sidebar}(\[.*?\])?(?:{(?P<title>.*?)})? + (?P<block_contents>.*?)\\end{sidebar}""", + flags=re.DOTALL + re.VERBOSE) def _sidebar_block(self, matchobj): block_contents = matchobj.group('block_contents') lines = block_contents.split('\n') - head = lines[0] - title = '' - additional = '' - - lines = lines[1:] - - matches = re.match(r"(\[.*\])?({.*?\})(.*)?", head) - if matches: - title = matches.group(2).strip() + title = matchobj.group('title') + if title: + title = title.replace("\n", " ").strip() title = get_text_in_brackets(title) - additional = matches.group(3).strip() - - if additional: - lines.insert(0, additional) lines = map(lambda line: line.strip(), lines) block_contents = '\n'.join(lines) block_contents = self.to_paragraph(block_contents) + block_contents = block_contents.replace("\n", " ") caret_token = self._caret_token if title: @@ -54,6 +46,8 @@ def _sidebargraphic_block(self, matchobj): block_contents = matchobj.group('block_contents') image = matchobj.group('block_graphics') block_name = matchobj.group('block_name') + if block_name: + block_name = block_name.replace('\n', ' ') if '.' not in image: ext = self._detect_asset_ext(image) @@ -64,15 +58,19 @@ def _sidebargraphic_block(self, matchobj): self._pdfs.append(image) image = image.replace('.pdf', '.jpg') - image_src = "![{}]({})".format(block_name, image) + image_src = "<img alt='{}' src='{}' style='width:200px' />".format(block_name, image) block_contents = self.to_paragraph(block_contents) caret_token = self._caret_token if block_name: + block_name = block_name.strip() return f'{caret_token}{caret_token}|||xdiscipline{caret_token}**{block_name}** ' \ f'{block_contents}{caret_token}{image_src}{caret_token}|||{caret_token}{caret_token}' + if not block_contents: + return f'{caret_token}{image_src}{caret_token}' + return f'{caret_token}{caret_token}|||xdiscipline{caret_token}{block_contents}' \ f'{caret_token}{image_src}{caret_token}|||{caret_token}{caret_token}' diff --git a/converter/markdown/tablefigure.py b/converter/markdown/tablefigure.py index 766ba7d8..59392342 100644 --- a/converter/markdown/tablefigure.py +++ b/converter/markdown/tablefigure.py @@ -27,11 +27,11 @@ def make_block(self, matchobj): self._matches.append(replace_token) self._figure_counter += 1 - caption = '**Figure {}.{}: '.format( + caption = '**<p style="font-size: 10px">Figure {}.{}: '.format( self._chapter_num, self._figure_counter + self._figure_counter_offset ) if self._refs.get(label, {}): - caption = '**Figure {}: '.format( + caption = '**<p style="font-size: 10px">Figure {}: '.format( self._refs.get(label).get('ref') ) @@ -51,7 +51,9 @@ def remove_matched_token(self, output, chars): caret_token = self._caret_token content = output[pos + token_len:index - 1] content = content.strip() - output = output[0:pos] + content + "**" + caret_token + output[index + 1:] + content = re.sub(r"\\{", r"{", content) + content = re.sub(r"\\}", r"}", content) + output = output[0:pos] + content + "</p>**" + caret_token + output[index + 1:] break else: level += 1 @@ -63,6 +65,7 @@ def convert(self): output = self.str output = table_re.sub(self.make_block, output) + output = re.sub(r"\\protect\\index{.*?}%?", "", output) for token in self._matches: output = self.remove_matched_token(output, token) diff --git a/converter/markdown/tabular.py b/converter/markdown/tabular.py index d4b750f7..07f620fd 100644 --- a/converter/markdown/tabular.py +++ b/converter/markdown/tabular.py @@ -9,6 +9,9 @@ class Tabular(TextAsParagraph): def __init__(self, latex_str, caret_token): super().__init__(latex_str, caret_token) + self._graphics_re = re.compile(r"\\includegraphics(\[.*?]){(.*?)}", + flags=re.DOTALL + re.VERBOSE) + self._table_re = re.compile(r"""\\begin{(?P<block_name>tabular)} (?P<block_contents>.*?) \\end{(?P=block_name)}""", @@ -21,14 +24,14 @@ def safe_list_get(self, list, idx, default): return default def _format_table(self, matchobj): + caret_token = self._caret_token block_contents = matchobj.group('block_contents') - block_contents = block_contents.strip() sub_lines = block_contents.split('\n') size = get_text_in_brackets(sub_lines[0]) block_contents = '\n'.join(sub_lines[1:]) block_contents = block_contents.replace('\\hline', '') - + block_contents = block_contents.replace('\\raggedright', '') token = str(uuid.uuid4()) items = block_contents.split('\\\\') @@ -43,10 +46,42 @@ def _format_table(self, matchobj): continue pos = 0 row = row.replace('\\&', token) - for ind in range(0, len(table_size)): + + row = re.sub(r"\\multicolumn{(.*?)}{(.*?)}{(.*?)}", r"\3", row, flags=re.DOTALL + re.VERBOSE) + row = re.sub(r"\\multirow{(.*?)}{(.*?)}\s?{(.*?)}", r"\3", row, flags=re.DOTALL + re.VERBOSE) + + tabularline_match = re.search(r"\\tabularline", block_contents, flags=re.DOTALL + re.VERBOSE) + if tabularline_match: + head = True + for line in block_contents.split('\n'): + line = line.replace('\\\\', '<br/>').strip() + line = line.strip() + if not line: + continue + if '\\tabularline' in line and head: + out += f'{caret_token}|{line}|{caret_token}|-|{caret_token}|' + head = False + continue + if '\\tabularline' in line and not head: + out += f'|{caret_token}' + head = True + continue + out += f'{line} ' + out = out.replace('\\tabularline', '') + break + + t_size = len(table_size) + + match = self._graphics_re.search(row) + if match: + row = row.replace('\\icondir', 'icons') + row = self._graphics_re.sub(r"<img alt='' src='\2' style='width:100px'/>", row) + t_size = len(row.split("&")) + + for ind in range(0, t_size): data = row.split('&') col = self.safe_list_get(data, ind, '').strip() - col = col.replace('\n', '<br/>') + col = re.sub(r'\s*\n\s*', ' ', col) col = col.replace('\\\\', '') out += "|" + col.replace('|', '|') if heading: diff --git a/converter/markdown/tags.py b/converter/markdown/tags.py new file mode 100644 index 00000000..94b7665f --- /dev/null +++ b/converter/markdown/tags.py @@ -0,0 +1,14 @@ +import re + + +class Tags(object): + def __init__(self, latex_str): + self.str = latex_str + + def convert(self): + output = self.str + + output = re.sub(r"\\tl", r"<", output) + output = re.sub(r"\\tg", r">", output) + + return output diff --git a/converter/markdown/textfigure.py b/converter/markdown/textfigure.py new file mode 100644 index 00000000..e496b4c0 --- /dev/null +++ b/converter/markdown/textfigure.py @@ -0,0 +1,23 @@ +import re + +from converter.markdown.text_as_paragraph import TextAsParagraph + + +class Textfigure(TextAsParagraph): + def __init__(self, latex_str, caret_token): + super().__init__(latex_str, caret_token) + self.str = latex_str + self._textfigure_re = re.compile(r"""\\begin{textfigure}(?P<block_contents>.*?)\\end{textfigure}""", + flags=re.DOTALL) + + def _textfigure_block(self, matchobj): + block_contents = matchobj.group('block_contents') + block_contents = self.to_paragraph(block_contents) + + return f'{block_contents}' + + def convert(self): + output = self.str + output = self._textfigure_re.sub(self._textfigure_block, output) + + return output diff --git a/converter/markdown/turingwinner.py b/converter/markdown/turingwinner.py new file mode 100644 index 00000000..5d68ae9e --- /dev/null +++ b/converter/markdown/turingwinner.py @@ -0,0 +1,51 @@ +from converter.markdown.match_elements import match_elements +from converter.markdown.text_as_paragraph import TextAsParagraph + + +class TuringWinner(TextAsParagraph): + def __init__(self, latex_str, caret_token, detect_asset_ext): + super().__init__(latex_str, caret_token) + self._pdfs = [] + self._detect_asset_ext = detect_asset_ext + + def make_content(self, image, block_name, block_contents, q_content, q_name): + if '.' not in image: + ext = self._detect_asset_ext(image) + if ext: + image = '{}.{}'.format(image, ext) + + if image.lower().endswith('.pdf'): + self._pdfs.append(image) + image = image.replace('.pdf', '.jpg') + + image_src = "<img alt='{}' src='{}' style='width:200px' />".format(block_name, image) + + block_contents = self.to_paragraph(block_contents) + caret_token = self._caret_token + + block_name = block_name.strip() + sidebar = f'{caret_token}{caret_token}|||xdiscipline{caret_token}**{block_name}** ' \ + f'{block_contents}{caret_token}{image_src}{caret_token}|||{caret_token}{caret_token}' + + quote = '' + q_name = q_name.strip() + if q_name and q_content: + quote = f'{caret_token}> {q_content}{caret_token}>' \ + f'{caret_token}> __{q_name}__{caret_token}{caret_token}' + + return sidebar + quote + + def convert(self): + out = self.str + search_str = "\\turingwinner" + pos = out.find(search_str) + while pos != -1: + matches, index = match_elements(out[pos + len(search_str):], 5) + start = out[0:pos] + end_pos = pos + len(search_str) + 1 + index + end = out[end_pos:] + out = start + self.make_content( + f'turing/figs/{matches[0]}', matches[1], matches[2], matches[3], matches[4] + ) + end + pos = out.find(search_str, end_pos + 1) + return out, self._pdfs diff --git a/converter/markdown/unescape.py b/converter/markdown/unescape.py index 91ee7e1d..2a1a4b02 100644 --- a/converter/markdown/unescape.py +++ b/converter/markdown/unescape.py @@ -8,8 +8,6 @@ def __init__(self, latex_str): def convert(self): output = self.str - output = re.sub(r"\\{", r"{", output) - output = re.sub(r"\\}", r"}", output) output = re.sub(r"\\#", "#", output) output = re.sub(r"\\_", "_", output) output = re.sub(r"\\-", "-", output) diff --git a/converter/refs.py b/converter/refs.py index daf33715..64597ef0 100644 --- a/converter/refs.py +++ b/converter/refs.py @@ -64,6 +64,7 @@ def make_refs(toc, chapter_counter_from=1): figs_counter = 0 is_figure = False is_exercise = False + line_break = False for item in toc: if item.section_type == CHAPTER: @@ -77,6 +78,14 @@ def make_refs(toc, chapter_counter_from=1): else: section_counter += 1 for line in item.lines: + if line.startswith("\\sectionfile"): + result = re.search(r"\\sectionfile(\[.*?\])?{(?P<block_name>.*?)}{(?P<block_path>.*?)}", line) + label = result.group("block_path") + if label: + refs[label] = { + 'pageref': item.section_name + } + refs[label]["ref"] = f'{chapter_counter}.{section_counter}' if line.startswith("\\begin{figure}"): figs_counter += 1 is_figure = True @@ -87,16 +96,44 @@ def make_refs(toc, chapter_counter_from=1): is_exercise = True elif line.startswith("\\end{exercise}"): is_exercise = False - elif "figure{" in line: - result = re.match(r'\\(?P<block_name>pic|table|codefile)figure{(?P<path>.*?)\}{(?P<ref>.*?)\}', line) + elif "figure{" in line or "figure[" in line: + result = re.search(r'\\(?P<block_name>pic|table|codefile)figure(\[.*\])?{(?P<path>.*?)}' + r'(%?\s*)?({(?P<ref>.*?)})?', line) if result: ref = result.group('ref') + if ref: + figs_counter += 1 + refs[ref] = { + 'pageref': item.section_name + } + refs[ref]["ref"] = f'{chapter_counter}.{figs_counter}' + else: + line_break = True + continue + elif line_break: + res = re.search(r'{(?P<ref>fig:.*?)}', line) + if res: + ref = res.group('ref') figs_counter += 1 refs[ref] = { 'pageref': item.section_name } refs[ref]["ref"] = f'{chapter_counter}.{figs_counter}' - elif "\\label{" in line: + line_break = False + elif "\\begin{enumerate}" in line: + items_counter = 0 + for ln in item.lines: + if "\\item" in ln: + items_counter += 1 + result = re.search(r'\\item\\label{(?P<ref>item:.*?)}', ln) + if result: + ref = result.group('ref') + refs[ref] = { + 'item_num': items_counter + } + if "\\end{enumerate}" in ln: + items_counter = 0 + elif "\\label{" in line and not line.startswith("\\item"): start_pos = line.find("\\label{") end_pos = line.find("}", start_pos) label = line[start_pos + 7:end_pos] diff --git a/converter/toc.py b/converter/toc.py index c4719407..a9cf3f48 100644 --- a/converter/toc.py +++ b/converter/toc.py @@ -14,7 +14,7 @@ def is_section(line): def is_chapter(line): - return line.startswith('\\chapter') + return line.startswith('\\chapter[') or line.startswith('\\chapter{') def is_toc(line): @@ -40,6 +40,7 @@ def include_file(line): def cleanup_name(name): + name = convert_name(name) l_pos = name.find('{') r_pos = name.find('}') cut_pos = l_pos + 1 @@ -60,6 +61,15 @@ def cleanup_name(name): return name +def convert_name(name): + name = re.sub(r"\\js({\})?", "JavaScript", name) + name = re.sub("``", "“", name) + name = re.sub("''", "”", name) + name = re.sub(r"--", "-", name) + name = re.sub(r" vs.\\", " vs.", name) + return name + + def get_name(line): level = 0 start = 0 @@ -76,7 +86,7 @@ def get_name(line): break else: level -= 1 - return cleanup_name(line[start + 1:end]) + return cleanup_name(line[start + 1:end]), line[end + 1:].strip() def get_bookdown_name(line): @@ -92,12 +102,14 @@ def is_section_file(line): def get_section_lines(line, tex_folder): - section_line_re = re.compile(r"""\\sectionfile{(?P<block_name>.*?)}{(?P<block_path>.*?)}""") + section_line_re = re.compile(r"""\\sectionfile(\[.*?\])?{(?P<block_name>.*?)}{(?P<block_path>.*?)}""") result = section_line_re.search(line) if result: file = result.group("block_path") if '.tex' not in file: - file = '_{}.tex'.format(file) + dirname = Path(tex_folder).name + prefix = dirname.split('ch_')[-1] + file = f'{prefix}_{file}.tex' tex_file = tex_folder.joinpath(file) if tex_file.exists(): with open(tex_file, 'r', errors='replace') as file: @@ -106,23 +118,66 @@ def get_section_lines(line, tex_folder): return [] +def cleanup_ifoddpage(lines): + updated = [] + ifoddpage = False + for line in lines: + if "\\checkoddpage" in line: + continue + if ifoddpage and "\\fi" in line: + ifoddpage = False + continue + if ifoddpage: + continue + if "\\ifoddpage{" in line: + line = re.sub(r"\\ifoddpage{\\small(\\input{.*?})}", r"\1", line, flags=re.DOTALL) + line = line.strip() + ifoddpage = True + updated.append(line) + return updated + + def process_toc_lines(lines, tex_folder, parent_folder): toc = [] line_pos = 1 item_lines = [] + chapter_line = '' + chapter_line_break = False for line in lines: line = line.rstrip('\r\n') + if chapter_line_break: + if line.endswith('}'): + line = chapter_line + line + chapter_line_break = False + else: + chapter_line += line + continue if is_toc(line): if toc: if item_lines: toc[len(toc) - 1].lines = item_lines item_lines = [] section_type = CHAPTER if is_chapter(line) else SECTION - toc.append(SectionItem(section_name=get_name(line), section_type=section_type, line_pos=line_pos)) + if section_type == CHAPTER and not line.endswith('}'): + chapter_line = line + chapter_line_break = True + continue + section_name, additional_content_in_name = get_name(line) + toc.append(SectionItem(section_name=section_name, section_type=section_type, line_pos=line_pos)) if is_section_file(line): section_lines = get_section_lines(line, parent_folder) + section_lines = cleanup_ifoddpage(section_lines) for sub_line in section_lines: - item_lines.append(sub_line.rstrip('\n')) + if is_input(sub_line): + sub_content = get_include_lines(tex_folder, input_file(sub_line)) + if sub_content: + item_lines.extend(sub_content) + line_pos += len(sub_content) + else: + item_lines.append(sub_line.rstrip('\n')) + elif additional_content_in_name: + item_lines.append(additional_content_in_name) + line_pos += 1 elif is_input(line) or is_include(line): if is_input(line): sub_toc = get_latex_toc(tex_folder, input_file(line)) diff --git a/requirements.txt b/requirements.txt index f74691aa..976dea9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ pdf2image==1.13.1 Pillow==7.2.0 -PyYAML==5.3.1 +PyYAML==5.3.1 \ No newline at end of file diff --git a/tests/unit/cases/T.md b/tests/unit/cases/T.md index 5c92f5fb..1601404e 100644 --- a/tests/unit/cases/T.md +++ b/tests/unit/cases/T.md @@ -1 +1 @@ -HTTP methods---**GET**, **POST**, **PUT** and **DELETE**---and even use \ No newline at end of file +HTTP methods---<b>GET</b>, <b>POST</b>, <b>PUT</b> and <b>DELETE</b>---and even use \ No newline at end of file diff --git a/tests/unit/cases/bc.md b/tests/unit/cases/bc.md index b4b1ca2e..aabcbfac 100644 --- a/tests/unit/cases/bc.md +++ b/tests/unit/cases/bc.md @@ -1,11 +1,11 @@ -**Summary of legacy code exploration** +<b>Summary of legacy code exploration</b> -**Voucher** +<b>Voucher</b> -jQuery defines a global function **jQuery()** (aliased as **<span>\$</span>()**) that, when passed a CSS selector (examples of which we saw in Figure fig:css_cheat), returns all of the current page's DOM elements matching that selector. For example, **jQuery('#movies')** or **<span>\$</span>('#movies')** would return the single element whose ID is **movies**, if one exists on the page; **<span>\$</span>('h1.title')** would return all the **h1** elements whose CSS class is **title**. A more general version of this functionality is **.find(***selector***)**, which only searches the DOM subtree rooted at the target. To illustrate the distinction, **<span>\$</span>('p span')** finds *any* **span** element that is contained inside a **p** element, whereas if **elt** already refers to a *particular* **p** element, then **elt.find('span')** only finds **span** elements that are descendants of **elt**. +jQuery defines a global function <b>jQuery()</b> (aliased as <b><span>\$</span>()</b>) that, when passed a CSS selector (examples of which we saw in Figure fig:css_cheat), returns all of the current page's DOM elements matching that selector. For example, <b>jQuery('#movies')</b> or <b><span>\$</span>('#movies')</b> would return the single element whose ID is <b>movies</b>, if one exists on the page; <b><span>\$</span>('h1.title')</b> would return all the <b>h1</b> elements whose CSS class is <b>title</b>. A more general version of this functionality is <b>.find(</b><i>selector</i><b>)</b>, which only searches the DOM subtree rooted at the target. To illustrate the distinction, <b><span>\$</span>('p span')</b> finds <i>any</i> <b>span</b> element that is contained inside a <b>p</b> element, whereas if <b>elt</b> already refers to a <i>particular</i> <b>p</b> element, then <b>elt.find('span')</b> only finds <b>span</b> elements that are descendants of <b>elt</b>. |||info -The call **jQuery.noConflict()** “undefines” the **<span>\$</span>** alias, in case your app uses the browser's built-in **<span>\$</span>** (usually an alias for **document.-getElementById**) or loads another JavaScript library such as [Prototype](http://prototypejs.org) that also tries to define **<span>\$</span>**. +The call <b>jQuery.noConflict()</b> “undefines” the <b><span>\$</span></b> alias, in case your app uses the browser's built-in <b><span>\$</span></b> (usually an alias for <b>document.getElementById</b>) or loads another JavaScript library such as [Prototype](http://prototypejs.org) that also tries to define <b><span>\$</span></b>. -||| \ No newline at end of file +||| diff --git a/tests/unit/cases/bf_bracket.md b/tests/unit/cases/bf_bracket.md index 735697f2..49f1daf0 100644 --- a/tests/unit/cases/bf_bracket.md +++ b/tests/unit/cases/bf_bracket.md @@ -1 +1 @@ -An important skill for a computer scientist is **problem solving**. \ No newline at end of file +An important skill for a computer scientist is <b>problem solving</b>. \ No newline at end of file diff --git a/tests/unit/cases/checkyourself.md b/tests/unit/cases/checkyourself.md index 437f1d1e..89b20505 100644 --- a/tests/unit/cases/checkyourself.md +++ b/tests/unit/cases/checkyourself.md @@ -1,6 +1,7 @@ |||challenge +True or False: User stories on 3x5 cards in BDD play the same role as design requirements in plan-and-document. +<p><details><summary>Check yourself</summary> -True or False: User stories on 3x5 cards in BDD play the same role as design requirements in plan-and-document. -<details><summary>Check yourself</summary>True.</details> +True.</details></p> ||| \ No newline at end of file diff --git a/tests/unit/cases/checkyourself_complex.md b/tests/unit/cases/checkyourself_complex.md index 03f1d714..77169fe5 100644 --- a/tests/unit/cases/checkyourself_complex.md +++ b/tests/unit/cases/checkyourself_complex.md @@ -1,8 +1,7 @@ |||challenge +Suppose you mix <b>Enumerable</b> into a class <b>Foo</b> that does not provide the <b>each</b> method. What error will be raised when you call <b>Foo.new.map { |elt| puts elt }</b>? +<p><details><summary>Check yourself</summary> - Suppose you mix **Enumerable** into a class **Foo** that does not - provide the **each** method. What error will be raised when you - call **Foo.new.map { |elt| puts elt }**? - <details><summary>Check yourself</summary>The **map** method in **Enumerable** will attempt to call **each** on its receiver, but since the new **Foo** object doesn't define **each**, Ruby will raise an Undefined Method error.</details> +The <b>map</b> method in <b>Enumerable</b> will attempt to call <b>each</b> on its receiver, but since the new <b>Foo</b> object doesn't define <b>each</b>, Ruby will raise an Undefined Method error.</details></p> ||| \ No newline at end of file diff --git a/tests/unit/cases/chips.md b/tests/unit/cases/chips.md index 84ada25f..a64c3fc3 100644 --- a/tests/unit/cases/chips.md +++ b/tests/unit/cases/chips.md @@ -1,4 +1,4 @@ -## Regular Expressions -_**Regular expressions**_, sometimes abbreviated *regexps* or *regexes*, are sequences of characters that define a search pattern. A regex can be applied to a string to determine whether the string contains those patterns. Given the preponderance of string manipulation in interactive and Web applications, all modern programming languages support regexes either within the language or as part of a library. This exercise will help you refresh your skills at constructing regular expressions. +## ActiveRecord Basics +https://github.com/saasbook/hw-activerecord-practice -(Other skill “refreshers” needed? Some general questions about encapsulation and inheritance, before moving on to concrete examples in Ruby?) \ No newline at end of file +Although ActiveRecord is part of Rails, the ActiveRecord subsystem can be used outside of Rails apps. In this assignment, you will write various ActiveRecord operations to manipulate a database of (fictional) customers for whom we store names, email addresses, and birthdays. In a typical Rails app, such queries usually appear in the model code, but working on these simple examples will help familiarize you with ActiveRecord's basic features before writing such code yourself diff --git a/tests/unit/cases/chips.tex b/tests/unit/cases/chips.tex index 0ab16a2e..102c194a 100644 --- a/tests/unit/cases/chips.tex +++ b/tests/unit/cases/chips.tex @@ -1,16 +1,10 @@ -\begin{chips}{Regular Expressions} - - \w{Regular expressions}, sometimes abbreviated \emph{regexps} or - \emph{regexes}, are sequences of characters that define a search - pattern. A regex can be applied to a string to determine whether - the string contains those patterns. Given the preponderance of - string manipulation in interactive and Web applications, all modern - programming languages support regexes either within the language or - as part of a library. This exercise will help you refresh your - skills at constructing regular expressions. - - (Other skill ``refreshers'' needed? Some general questions about - encapsulation and inheritance, before moving on to concrete examples - in Ruby?) - +\begin{chips}{ActiveRecord Basics}{https://github.com/saasbook/hw-activerecord-practice} + Although ActiveRecord is part of Rails, the ActiveRecord subsystem + can be used outside of Rails apps. + In this assignment, you will write various ActiveRecord operations + to manipulate a database of (fictional) customers for whom we store + names, email addresses, and birthdays. In a typical Rails app, such queries + usually appear in the model code, but working on these simple examples + will help familiarize you with ActiveRecord's basic features before + writing such code yourself \end{chips} \ No newline at end of file diff --git a/tests/unit/cases/codefigure.md b/tests/unit/cases/codefigure.md index d447c93f..3080c256 100644 --- a/tests/unit/cases/codefigure.md +++ b/tests/unit/cases/codefigure.md @@ -1,5 +1,5 @@ **Figure 1.1** -**source:ch_javascript/code/json_example.js** +**source: ch_javascript/code/json_example.js** ```code file content ``` @@ -7,16 +7,16 @@ file content **Figure 1.2** -**source:ch_ruby_rails/code/class_example.rb** +**source: ch_ruby_rails/code/class_example.rb** ```code file content ``` - A simple class definition in Ruby showing that explicit getter and setter methods are the only way to access instance variables from outside a class, and that Ruby provides shortcuts (lines 19--20) that avoid having to define every accessor method explicitly. Rather than distinguish “private” vs.\ “public” instance and class variables, one simply provides public accessor methods (read-only, write-only, or read/write) for state that should be publicly visible. + A simple class definition in Ruby showing that explicit getter and setter methods are the only way to access instance variables from outside a class, and that Ruby provides shortcuts (lines 19--20) that avoid having to define every accessor method explicitly. Rather than distinguish “private” vs. “public” instance and class variables, one simply provides public accessor methods (read-only, write-only, or read/write) for state that should be publicly visible. **Figure 1.3** -**source:ch_ruby_rails/code/class_example.rb** +**source: ch_ruby_rails/code/class_example.rb** ```code file content ``` - A simple class definition in Ruby showing that explicit getter and setter methods are the only way to access instance variables from outside a class, and that Ruby provides shortcuts (lines 19--20) that avoid having to define every accessor method explicitly. Rather than distinguish “private” vs.\ “public” instance and class variables, one simply provides public accessor methods (read-only, write-only, or read/write) for state that should be publicly visible. \ No newline at end of file + A simple class definition in Ruby showing that explicit getter and setter methods are the only way to access instance variables from outside a class, and that Ruby provides shortcuts (lines 19--20) that avoid having to define every accessor method explicitly. Rather than distinguish “private” vs. “public” instance and class variables, one simply provides public accessor methods (read-only, write-only, or read/write) for state that should be publicly visible. diff --git a/tests/unit/cases/comments.md b/tests/unit/cases/comments.md index abff8f21..2312afa9 100644 --- a/tests/unit/cases/comments.md +++ b/tests/unit/cases/comments.md @@ -1,4 +1,4 @@ -Many people (and textbooks) incorrectly refer to `%` as the “modulus operator”. In mathematics, however, **modulus** is the number you're dividing by. In the previous example, the modulus is 12. The Java language specification refers to `%` as the “remainder operator”. +Many people (and textbooks) incorrectly refer to `%` as the “modulus operator”. In mathematics, however, <b>modulus</b> is the number you're dividing by. In the previous example, the modulus is 12. The Java language specification refers to `%` as the “remainder operator”. The remainder operator looks like a percent sign, but you might find it helpful to think of it as a division sign ($\div$) rotated to the left. diff --git a/tests/unit/cases/concepts.md b/tests/unit/cases/concepts.md index 41839a1b..f1b82b8b 100644 --- a/tests/unit/cases/concepts.md +++ b/tests/unit/cases/concepts.md @@ -1,10 +1,10 @@ ## Concepts -_**JavaScript**_ is a dynamic, interpreted scripting language built into modern browsers. This chapter describes its main features, including some that we recommend avoiding because they represent questionable design choices, and how it extends the types of content and applications that can be delivered as SaaS. +___JavaScript___ is a dynamic, interpreted scripting language built into modern browsers. This chapter describes its main features, including some that we recommend avoiding because they represent questionable design choices, and how it extends the types of content and applications that can be delivered as SaaS. -* A browser represents a web page as a data structure called the _**Document Object Model**_ (DOM). JavaScript code running in the browser can inspect and modify this data structure, causing the browser to redraw the modified page elements. -* When a user interacts with the browser (for example, by typing, clicking, or moving the mouse) or the browser makes progress in an interaction with a server, the browser generates an _**event**_ indicating what happened. Your JavaScript code can take app-specific actions to modify the DOM when such events occur. -* Using _**AJAX**_, or Asynchronous JavaScript And XML, JavaScript code can make HTTP requests to a Web server *without* triggering a page reload. The information in the response can then be used to modify page elements in place, giving a richer and often more responsive user experience than traditional Web pages. Rails partials and controller actions can be readily used to handle AJAX interactions. -* Just as we use the highly-productive Rails framework (Chapter chap:rails_intro) and RSpec TDD tool (Chapter chap:tdd) for server-side SaaS code, here we use the highly-productive _**jQuery**_ framework and [Jasmine](http://pivotal.github.com/jasmine) TDD tool to develop client-side code. -* We follow the best practice of “graceful degradation,” also referred to as “progressive enhancement”: legacy browsers lacking JavaScript support will still provide a good user experience, while JavaScript-enabled browsers will provide an even better experience. \ No newline at end of file +* A browser represents a web page as a data structure called the ___Document Object Model___ (DOM). JavaScript code running in the browser can inspect and modify this data structure, causing the browser to redraw the modified page elements. +* When a user interacts with the browser (for example, by typing, clicking, or moving the mouse) or the browser makes progress in an interaction with a server, the browser generates an ___event___ indicating what happened. Your JavaScript code can take app-specific actions to modify the DOM when such events occur. +* Using ___AJAX___, or Asynchronous JavaScript And XML, JavaScript code can make HTTP requests to a Web server <i>without</i> triggering a page reload. The information in the response can then be used to modify page elements in place, giving a richer and often more responsive user experience than traditional Web pages. Rails partials and controller actions can be readily used to handle AJAX interactions. +* Just as we use the highly-productive Rails framework (Chapter chap:rails_intro) and RSpec TDD tool (Chapter chap:tdd) for server-side SaaS code, here we use the highly-productive ___jQuery___ framework and [Jasmine](http://pivotal.github.com/jasmine) TDD tool to develop client-side code. +* We follow the best practice of “graceful degradation,” also referred to as “progressive enhancement”: legacy browsers lacking JavaScript support will still provide a good user experience, while JavaScript-enabled browsers will provide an even better experience. diff --git a/tests/unit/cases/dedicationwithpic.md b/tests/unit/cases/dedicationwithpic.md index b50a72ec..d681ba55 100644 --- a/tests/unit/cases/dedicationwithpic.md +++ b/tests/unit/cases/dedicationwithpic.md @@ -1,3 +1,3 @@ -![David Patterson dedicates this book to his parents and all their descendants:<br/>---To my father David, from whom I inherited inventiveness, athleticism, and the courage to fight for what is right;<br/>---To my mother Lucie, from whom I inherited intelligence, optimism, and my temperament;<br/>---To our sons David and Michael, who are friends, athletic companions, and inspirations for me to be a good man;<br/>---To our daughters-in-law Heather and Zackary, who are smart, funny, and caring mothers to our grandchildren;<br/>---To our grandchildren Andrew, Grace, and Owyn, who give us our chance at immortality (and who helped with marketing for this book);<br/>---To my younger siblings Linda, Don, and Sue, who gave me my first chance to teach;<br/>---To their descendants, who make the Patterson clan both large and fun to be with;<br/>---And to my beautiful and understanding wife Linda, who is my best friend and the love of my life.](ch_foreword/figs/Pattersons.jpg) +![David Patterson dedicates this book to his parents and all their descendants:<br/> ---To my father David, from whom I inherited inventiveness, athleticism, and the courage to fight for what is right;<br/> ---To my mother Lucie, from whom I inherited intelligence, optimism, and my temperament;<br/> ---To our sons David and Michael, who are friends, athletic companions, and inspirations for me to be a good man;<br/> ---To our daughters-in-law Heather and Zackary, who are smart, funny, and caring mothers to our grandchildren;<br/> ---To our grandchildren Andrew, Grace, and Owyn, who give us our chance at immortality (and who helped with marketing for this book);<br/> ---To my younger siblings Linda, Don, and Sue, who gave me my first chance to teach;<br/> ---To their descendants, who make the Patterson clan both large and fun to be with;<br/> ---And to my beautiful and understanding wife Linda, who is my best friend and the love of my life.](ch_foreword/figs/Pattersons.jpg) -David Patterson dedicates this book to his parents and all their descendants:<br/>---To my father David, from whom I inherited inventiveness, athleticism, and the courage to fight for what is right;<br/>---To my mother Lucie, from whom I inherited intelligence, optimism, and my temperament;<br/>---To our sons David and Michael, who are friends, athletic companions, and inspirations for me to be a good man;<br/>---To our daughters-in-law Heather and Zackary, who are smart, funny, and caring mothers to our grandchildren;<br/>---To our grandchildren Andrew, Grace, and Owyn, who give us our chance at immortality (and who helped with marketing for this book);<br/>---To my younger siblings Linda, Don, and Sue, who gave me my first chance to teach;<br/>---To their descendants, who make the Patterson clan both large and fun to be with;<br/>---And to my beautiful and understanding wife Linda, who is my best friend and the love of my life. \ No newline at end of file +David Patterson dedicates this book to his parents and all their descendants:<br/> ---To my father David, from whom I inherited inventiveness, athleticism, and the courage to fight for what is right;<br/> ---To my mother Lucie, from whom I inherited intelligence, optimism, and my temperament;<br/> ---To our sons David and Michael, who are friends, athletic companions, and inspirations for me to be a good man;<br/> ---To our daughters-in-law Heather and Zackary, who are smart, funny, and caring mothers to our grandchildren;<br/> ---To our grandchildren Andrew, Grace, and Owyn, who give us our chance at immortality (and who helped with marketing for this book);<br/> ---To my younger siblings Linda, Don, and Sue, who gave me my first chance to teach;<br/> ---To their descendants, who make the Patterson clan both large and fun to be with;<br/> ---And to my beautiful and understanding wife Linda, who is my best friend and the love of my life. \ No newline at end of file diff --git a/tests/unit/cases/em_bracket.md b/tests/unit/cases/em_bracket.md index 8ab4dd1a..0055b221 100644 --- a/tests/unit/cases/em_bracket.md +++ b/tests/unit/cases/em_bracket.md @@ -1 +1 @@ -*One concept at a time.* \ No newline at end of file +<i>One concept at a time.</i> \ No newline at end of file diff --git a/tests/unit/cases/emph.md b/tests/unit/cases/emph.md index 2332926a..75288264 100644 --- a/tests/unit/cases/emph.md +++ b/tests/unit/cases/emph.md @@ -1,3 +1,3 @@ -and *controllers* that mediate the interaction between views and +and <i>controllers</i> that mediate the interaction between views and -*(This book uses sidebars to include what your authors think are interesting asides or short biographies of computing pioneers that supplement the primary text. We hope readers will enjoy them.)* \ No newline at end of file +<i>(This book uses sidebars to include what your authors think are interesting asides or short biographies of computing pioneers that supplement the primary text. We hope readers will enjoy them.)</i> \ No newline at end of file diff --git a/tests/unit/cases/enumerate.md b/tests/unit/cases/enumerate.md index fe540cc8..cbe75726 100644 --- a/tests/unit/cases/enumerate.md +++ b/tests/unit/cases/enumerate.md @@ -1,3 +1,29 @@ 1. Type in the hello world program, then compile and run it. -1. Add a print statement that displays a second message after the “Hello, World!”. Say something witty like, “How are you?” Compile and run the program again. -1. Add a comment to the program (anywhere), recompile, and run it again. The new comment should not affect the result. \ No newline at end of file +2. Add a print statement that displays a second message after the “Hello, World!”. Say something witty like, “How are you?” Compile and run the program again. +3. Add a comment to the program (anywhere), recompile, and run it again. The new comment should not affect the result. + + + +Recall that the hope for plan-and-document methods is to make software engineering as predictable in budget and schedule as civil engineering. Remarkably, user stories, points, and velocity correspond to <i>seven</i> major tasks of the plan-and-document methodologies. They include: + + +1. Requirements Elicitation +2. Requirements Documentation +3. Cost Estimation +4. Scheduling and Monitoring Progress + + +These are done up front for the Waterfall model and at the beginning of each major iteration for the Spiral and RUP models. As requirements change over time, these items above imply other tasks: + + +5. Change Management for Requirements, Cost, and Schedule +6. Ensuring Implementation Matches Requirement Features + + +Finally, since accuracy of the budget estimate and the schedule is vital to the success of the plan-and-document process, there is another task not found in BDD: + + +7. Risk Analysis and Management + + +The hope is that by imagining all the risks to the budget and schedule in advance, the project can make plans to avoid or overcome them. diff --git a/tests/unit/cases/enumerate.tex b/tests/unit/cases/enumerate.tex index c63e3ece..62c26f29 100644 --- a/tests/unit/cases/enumerate.tex +++ b/tests/unit/cases/enumerate.tex @@ -8,3 +8,24 @@ \item Add a comment to the program (anywhere), recompile, and run it again. The new comment should not affect the result. \end{enumerate} + +Recall that the hope for plan-and-document methods is to make software engineering as predictable in budget and schedule as civil engineering. Remarkably, user stories, points, and velocity correspond to \emph{seven} major tasks of the plan-and-document methodologies. They include: +\index{Plan-and-Document!major tasks}% +\begin{enumerate} + \item Requirements Elicitation + \item Requirements Documentation + \item Cost Estimation + \item Scheduling and Monitoring Progress +\end{enumerate} +\index{Waterfall lifecycle!tasks}\index{Spiral lifecycle!tasks}% +\index{Rational Unified Process (RUP)!tasks}% +These are done up front for the Waterfall model and at the beginning of each major iteration for the Spiral and RUP models. As requirements change over time, these items above imply other tasks: +\begin{enumerate} \addtocounter{enumi}{4} +\item Change Management for Requirements, Cost, and Schedule +\item Ensuring Implementation Matches Requirement Features +\end{enumerate} +Finally, since accuracy of the budget estimate and the schedule is vital to the success of the plan-and-document process, there is another task not found in BDD: +\begin{enumerate} \addtocounter{enumi}{6} +\item Risk Analysis and Management +\end{enumerate} +The hope is that by imagining all the risks to the budget and schedule in advance, the project can make plans to avoid or overcome them. diff --git a/tests/unit/cases/eqnarray.md b/tests/unit/cases/eqnarray.md index 602a6f21..90e7830e 100644 --- a/tests/unit/cases/eqnarray.md +++ b/tests/unit/cases/eqnarray.md @@ -9,12 +9,12 @@ $$ Write a recursive method called `ack` that takes two `int`s as parameters and that computes and returns the value of the Ackermann function. -For example, the **factorial** of an integer $n$, which is written $n!$, is defined like this: +For example, the <b>factorial</b> of an integer $n$, which is written $n!$, is defined like this: $$ 0! = 1 \\ n! = n \cdot(n-1)! $$ -Don't confuse the mathematical symbol $!$, which means *factorial*, with the Java operator `!`, which means *not*. +Don't confuse the mathematical symbol $!$, which means <i>factorial</i>, with the Java operator `!`, which means <i>not</i>. $$ \sin \frac{\pi}{4} + \frac{\cos \frac{\pi}{4}}{2} \\ diff --git a/tests/unit/cases/equation.md b/tests/unit/cases/equation.md new file mode 100644 index 00000000..67c7bd79 --- /dev/null +++ b/tests/unit/cases/equation.md @@ -0,0 +1,7 @@ +Finally, like performance, reliability can be measured. We can improve availability either taking longer between failures (MTTF) or by making the app reboot faster---___mean time between repairs___ (___MTTR___)---as this equation shows: +<center> +$$ +\mbox{unavailability} \approx \frac{\mbox{MTTR}}{\mbox{MTTF}} +$$ +</center> + While it is hard to measure improvements in MTTF, as it can take a long time to record failures, we can easily measure MTTR. We just crash a computer and see how long it takes the app to reboot. And what we can measure, we can improve. Hence, it may be much more cost-effective to try to improve MTTR than to improve MTTF since it is easier to measure progress. However, they are not mutually exclusive, so developers can try to increase dependability by following both paths. \ No newline at end of file diff --git a/tests/unit/cases/equation.tex b/tests/unit/cases/equation.tex new file mode 100644 index 00000000..19651654 --- /dev/null +++ b/tests/unit/cases/equation.tex @@ -0,0 +1,5 @@ +Finally, like performance, reliability can be measured. We can improve availability either taking longer between failures (MTTF) or by making the app reboot faster---\w{mean time between repairs} (\w[mean time between repairs]{MTTR})\index{Mean time between repairs (MTTR)}---as this equation shows: +\begin{equation} + \mbox{unavailability} \approx \frac{\mbox{MTTR}}{\mbox{MTTF}} +\end{equation} +While it is hard to measure improvements in MTTF, as it can take a long time to record failures, we can easily measure MTTR. We just crash a computer and see how long it takes the app to reboot. And what we can measure, we can improve. Hence, it may be much more cost-effective to try to improve MTTR than to improve MTTF since it is easier to measure progress. However, they are not mutually exclusive, so developers can try to increase dependability by following both paths. diff --git a/tests/unit/cases/esc_dol.md b/tests/unit/cases/esc_dol.md index 562da2a2..dcc89541 100644 --- a/tests/unit/cases/esc_dol.md +++ b/tests/unit/cases/esc_dol.md @@ -1,3 +1,2 @@ ![a) Study of software projects found that 53% of projects exceeding their budgets by a factor of 2.9 and overshot their schedule by a factor of 3.2 and another 31% of software projects were cancelled before completion (standish95). The estimated annual cost in the United States for such software projects was <span>\$</span>100B. b) Survey of members of the British Computer Society found that only 130 of 1027 projects met their schedule and budget. Half of all projects were maintenance or data conversion projects and half new development projects, but the successful projects divided into 127 of the former and just 3 of the latter (taylor00). c) Survey of 250 large projects, each with the equivalent of more than a million lines of C code, found similarly disappointing results (Jones04). d) Survey listing just the large examples of 50,000 projects, in that they cost at least <span>\$</span>10M in development (standish13). It has the most dismal outcomes, suggesting that HealthCare.gov had just a 10% chance of success.](ch_intro/figs/SoftwareProjectsSurveys3.jpg) -**Figure 1.1** -a) Study of software projects found that 53% of projects exceeding their budgets by a factor of 2.9 and overshot their schedule by a factor of 3.2 and another 31% of software projects were cancelled before completion (standish95). The estimated annual cost in the United States for such software projects was <span>\$</span>100B. b) Survey of members of the British Computer Society found that only 130 of 1027 projects met their schedule and budget. Half of all projects were maintenance or data conversion projects and half new development projects, but the successful projects divided into 127 of the former and just 3 of the latter (taylor00). c) Survey of 250 large projects, each with the equivalent of more than a million lines of C code, found similarly disappointing results (Jones04). d) Survey listing just the large examples of 50,000 projects, in that they cost at least <span>\$</span>10M in development (standish13). It has the most dismal outcomes, suggesting that HealthCare.gov had just a 10% chance of success. \ No newline at end of file +**<p style="font-size: 10px">Figure 1.1: a) Study of software projects found that 53% of projects exceeding their budgets by a factor of 2.9 and overshot their schedule by a factor of 3.2 and another 31% of software projects were cancelled before completion (standish95). The estimated annual cost in the United States for such software projects was <span>\$</span>100B. b) Survey of members of the British Computer Society found that only 130 of 1027 projects met their schedule and budget. Half of all projects were maintenance or data conversion projects and half new development projects, but the successful projects divided into 127 of the former and just 3 of the latter (taylor00). c) Survey of 250 large projects, each with the equivalent of more than a million lines of C code, found similarly disappointing results (Jones04). d) Survey listing just the large examples of 50,000 projects, in that they cost at least <span>\$</span>10M in development (standish13). It has the most dismal outcomes, suggesting that HealthCare.gov had just a 10% chance of success.</p>** \ No newline at end of file diff --git a/tests/unit/cases/href.md b/tests/unit/cases/href.md index af79e25d..b969236f 100644 --- a/tests/unit/cases/href.md +++ b/tests/unit/cases/href.md @@ -1 +1 @@ -If you have additional comments or ideas about the text, please send them to: [mailto:feedback@greenteapress.com](feedback@greenteapress.com). \ No newline at end of file +Despite design patterns' popularity as a tool, they have been the subject of some critique; for example, Peter Norvig, currently Google's Director of Research, [has argued](http://www.norvig.com/design-patterns) that some design patterns just compensate for deficiencies in statically-typed programming languages such as C++ and Java \ No newline at end of file diff --git a/tests/unit/cases/href.tex b/tests/unit/cases/href.tex index 4c90b312..74c7ba8f 100644 --- a/tests/unit/cases/href.tex +++ b/tests/unit/cases/href.tex @@ -1 +1,6 @@ -If you have additional comments or ideas about the text, please send them to: \href{mailto:feedback@greenteapress.com}{\tt feedback@greenteapress.com}. +Despite design patterns' popularity as a tool, +they have been the subject of some critique; for example, Peter +Norvig, currently Google's Director of Research, +\href{http://www.norvig.com/design-patterns}{has argued} that some design +patterns just compensate for deficiencies in statically-typed +programming languages such as C++ and Java \ No newline at end of file diff --git a/tests/unit/cases/href_simple.md b/tests/unit/cases/href_simple.md index c5a589f6..b969236f 100644 --- a/tests/unit/cases/href_simple.md +++ b/tests/unit/cases/href_simple.md @@ -1 +1 @@ -If you have additional comments or ideas about the text, please send them to: [mailto:feedback@greenteapress.com](feedback@greenteapress.com). +Despite design patterns' popularity as a tool, they have been the subject of some critique; for example, Peter Norvig, currently Google's Director of Research, [has argued](http://www.norvig.com/design-patterns) that some design patterns just compensate for deficiencies in statically-typed programming languages such as C++ and Java \ No newline at end of file diff --git a/tests/unit/cases/href_simple.tex b/tests/unit/cases/href_simple.tex index 80d965e2..74c7ba8f 100644 --- a/tests/unit/cases/href_simple.tex +++ b/tests/unit/cases/href_simple.tex @@ -1 +1,6 @@ -If you have additional comments or ideas about the text, please send them to: \href{mailto:feedback@greenteapress.com}{feedback@greenteapress.com}. +Despite design patterns' popularity as a tool, +they have been the subject of some critique; for example, Peter +Norvig, currently Google's Director of Research, +\href{http://www.norvig.com/design-patterns}{has argued} that some design +patterns just compensate for deficiencies in statically-typed +programming languages such as C++ and Java \ No newline at end of file diff --git a/tests/unit/cases/html.md b/tests/unit/cases/html.md index 9973cfcd..17766062 100644 --- a/tests/unit/cases/html.md +++ b/tests/unit/cases/html.md @@ -1,27 +1,29 @@ -In contrast to a native app, which is designed to render a particular user interface associated with only one SaaS service, we can think of a desktop or mobile browser as a *universal client*, because any site the browser visits can deliver all the information necessary to render that site's user interface. Both browsers and native apps are used by millions of people, so we call them *production clients*. +In contrast to a native app, which is designed to render a particular user interface associated with only one SaaS service, we can think of a desktop or mobile browser as a <i>universal client</i>, because any site the browser visits can deliver all the information necessary to render that site's user interface. Both browsers and native apps are used by millions of people, so we call them <i>production clients</i>. Indeed, modern practice suggests that even when creating a user-facing SaaS app designed to be used via a browser, we should design the app as a collection of resources accessible via RESTful APIs, but then provide a Web browser-based user interface “on top of” those API calls. -If the Web browser is the universal client, _**HTML**_, the HyperText Markup Language, is the universal language. A _**markup language**_ combines text with markup (annotations about the text) in a way that makes it easy to syntactically distinguish the two. +If the Web browser is the universal client, ___HTML___, the HyperText Markup Language, is the universal language. A ___markup language___ combines text with markup (annotations about the text) in a way that makes it easy to syntactically distinguish the two. -HTML consists of a hierarchy of nested elements, each of which consists of an opening tag such as **<p>**, a content part (in some cases), and a closing tag such as **</p>**. Most opening tags can also have attributes, as in **$<$a href="http://..."$>$**. Some tags that don't have a content part are self-closing, such as **<br clear="both"/>** for a line break that clears both left and right margins. +HTML consists of a hierarchy of nested elements, each of which consists of an opening tag such as <b><p></b>, a content part (in some cases), and a closing tag such as <b></p></b>. Most opening tags can also have attributes, as in <b>$<$a href="http://..."$>$</b>. Some tags that don't have a content part are self-closing, such as <b><br clear="both"/></b> for a line break that clears both left and right margins. |||info -The use of angle brackets for tags comes from _**SGML**_ (Standard Generalized Markup Language), a codified standardization of IBM's Generalized Markup Language, developed in the 1960s for encoding computer-readable project documents. +The use of angle brackets for tags comes from ___SGML___ (Standard Generalized Markup Language), a codified standardization of IBM's Generalized Markup Language, developed in the 1960s for encoding computer-readable project documents. ||| -There is an unfortunate and confusing mess of terminology surrounding the [lineage of HTML](http://www.w3.org/TR/html5/introduction.html#history-1). HTML 5 includes features of both its predecessors (HTML versions 1 through 4) and XHTML (eXtended HyperText Markup Language), which is a subset of _**XML**_, an eXtensible Markup Language that can be used both to represent data and to describe other markup languages. Indeed, XML is a common data representation for exchanging information *between* two services in a Service-Oriented Architecture, as we'll see in Chapter chap:tdd when we extend RottenPotatoes to retrieve movie information from a separate movie database service. The differences among the variants of XHTML and HTML are difficult to keep straight, and not all browsers support all versions. Unless otherwise noted, from now on when we say HTML we mean HTML 5, and we will try to avoid using features that aren't widely supported. +There is an unfortunate and confusing mess of terminology surrounding the [lineage of HTML](http://www.w3.org/TR/html5/introduction.html#history-1) +HTML 5 includes features of both its predecessors (HTML versions 1 through 4) and XHTML (eXtended HyperText Markup Language), which is a subset of ___XML___, an eXtensible Markup Language that can be used both to represent data and to describe other markup languages. Indeed, XML is a common data representation for exchanging information <i>between</i> two services in a Service-Oriented Architecture, as we'll see in Chapter chap:tdd when we extend RottenPotatoes to retrieve movie information from a separate movie database service. The differences among the variants of XHTML and HTML are difficult to keep straight, and not all browsers support all versions. Unless otherwise noted, from now on when we say HTML we mean HTML 5, and we will try to avoid using features that aren't widely supported. -Of particular interest are the HTML tag attributes **id** and **class**, because they figure heavily into connecting the HTML structure of a page with its visual appearance. The following screencast illustrates the use of Firefox's Web Developer toolbar to quickly identify the ID's and Classes of HTML elements on a page. + +Of particular interest are the HTML tag attributes <b>id</b> and <b>class</b>, because they figure heavily into connecting the HTML structure of a page with its visual appearance. The following screencast illustrates the use of Firefox's Web Developer toolbar to quickly identify the ID's and Classes of HTML elements on a page. <hr> @@ -30,7 +32,7 @@ Of particular interest are the HTML tag attributes **id** and **class**, because <iframe width="560" height="315" src="//www.youtube.com/embed/X5ArSbUea_o" frameborder="0" allowfullscreen></iframe> -CSS uses _**selector notations**_ such as **div#***name* to indicate a **div** element whose **id** is *name* and **div.***name* to indicate a **div** element with class *name*. Only one element in an HTML document can have a given **id**, whereas many elements (even of different tag types) can share the same **class**. All three aspects of an element---its tag type, its **id** (if it has one), and its **class** attributes (if it has any)---can be used to identify an element as a candidate for visual formatting. +CSS uses ___selector notations___ such as <b>div#</b><i>name</i> to indicate a <b>div</b> element whose <b>id</b> is <i>name</i> and <b>div.</b><i>name</i> to indicate a <b>div</b> element with class <i>name</i>. Only one element in an HTML document can have a given <b>id</b>, whereas many elements (even of different tag types) can share the same <b>class</b>. All three aspects of an element---its tag type, its <b>id</b> (if it has one), and its <b>class</b> attributes (if it has any)---can be used to identify an element as a candidate for visual formatting. <hr> @@ -43,7 +45,7 @@ For an extreme example of how much can be done with CSS, visit the [CSS Zen Gard ||| -As the next screencast shows, the _**CSS**_ (_**Cascading Style Sheets**_) standard allows us to associate visual “styling” instructions with HTML elements by using the elements' classes and IDs. The screencast covers only a few basic CSS constructs, which are summarized in Figure fig:css_cheat. The Resources section at the end of the chapter lists sites and books that describe CSS in great detail, including how to use CSS for aligning content on a page, something designers used to do manually with HTML tables. +As the next screencast shows, the ___CSS___ (___Cascading Style Sheets___) standard allows us to associate visual “styling” instructions with HTML elements by using the elements' classes and IDs. The screencast covers only a few basic CSS constructs, which are summarized in Figure fig:css_cheat. The Resources section at the end of the chapter lists sites and books that describe CSS in great detail, including how to use CSS for aligning content on a page, something designers used to do manually with HTML tables. @@ -54,7 +56,7 @@ As the next screencast shows, the _**CSS**_ (_**Cascading Style Sheets**_) stand <iframe width="560" height="315" src="//www.youtube.com/embed/E5ZVorHn_fs" frameborder="0" allowfullscreen></iframe> -There are four basic mechanisms by which a selector in a CSS file can match an HTML element: by tag name, by class, by ID, and by hierarchy. If multiple selectors match a given element, the rules for which properties to apply are complex, so most designers try to avoid such ambiguities by keeping their CSS simple. A useful way to see the “bones” of a page is to select \Sf{CSS>-Disable Styles>-All Styles} from the Firefox Web Developer toolbar; most developer-friendly browsers offer a “developer mode” featuring similar behaviors. Disabling styles will display the page with all CSS formatting turned off, showing the extent to which CSS can be used to separate visual appearance from logical structure. +There are four basic mechanisms by which a selector in a CSS file can match an HTML element: by tag name, by class, by ID, and by hierarchy. If multiple selectors match a given element, the rules for which properties to apply are complex, so most designers try to avoid such ambiguities by keeping their CSS simple. A useful way to see the “bones” of a page is to select \Sf{CSS>-Disable Styles>-All Styles} from the Firefox Web Developer toolbar; most developer-friendly browsers offer a “developer mode” featuring similar behaviors. Disabling styles will display the page with all CSS formatting turned off, showing the extent to which CSS can be used to separate visual appearance from logical structure. <hr> @@ -62,15 +64,15 @@ There are four basic mechanisms by which a selector in a CSS file can match an H ch_arch/tables/css_cheat -**Figure 1.1: A few CSS constructs, including those explained in Screencast css-intro. The top table shows some CSS *selectors*, which identify the elements to be styled; the bottom table shows a few of the many attributes, whose names are usually self-explanatory, and example values they can be assigned. Not all attributes are valid on all elements.** +**<p style="font-size: 10px">Figure 1.1: A few CSS constructs, including those explained in Screencast css-intro. The top table shows some CSS <i>selectors</i>, which identify the elements to be styled; the bottom table shows a few of the many attributes, whose names are usually self-explanatory, and example values they can be assigned. Not all attributes are valid on all elements.</p>** Using this new information, Figure fig:10k expands steps 2 and 3 from the previous section's summary of how SaaS works. ![SaaS from 10,000 feet. Compared to Figure fig:50k, step 2 has been expanded to describe the content returned by the Web server, and step 3 has been expanded to describe the role of CSS in how the Web browser renders the content.](ch_arch/figs/saas10k.jpg) -**Figure 1.3** -SaaS from 10,000 feet. Compared to Figure fig:50k, step 2 has been expanded to describe the content returned by the Web server, and step 3 has been expanded to describe the role of CSS in how the Web browser renders the content. +**<p style="font-size: 10px">Figure 1.3: SaaS from 10,000 feet. Compared to Figure fig:50k, step 2 has been expanded to describe the content returned by the Web server, and step 3 has been expanded to describe the role of CSS in how the Web browser renders the content.</p>** + CSS provides for sophisticated layout behaviors, but can be tricky to use in 2 regards. First, some background in layout and graphic design is helpful in deciding how to style site elements - spacing, typography, color palette. Second, even if you know what you want the site to look like, it can be tricky to write the necessary CSS to achieve complex layouts, in which elements may "float" to the far left or far right of the page while text flows around them, or rearrange themselves responsively when the screen geometry changes (browser window resized, phone rotates) to provide a defensible user experience on both desktop and mobile devices. @@ -83,19 +85,19 @@ JavaScript - more sophisticated behaviors such as animations, collapsing menus, Rather than delving deeply into the aesthetics of graphic design, our goal for you as a well-rounded full-stack developer is that you should know how to provide well-structured pages with proper element nesting and clean CSS class tags, for two reasons. First, with a good front-end framework, just doing this will be enough to provide an AURA site (aesthetic, usable, responsive, accessible). Second, a clean site layout and classing allows designers to refine, customize, or work separately on the site's visual appearance. There are 2 principles to using front-end frameworks successfully in this way: semantic styling and grid layout. Semantic styling means First, think of your page's visual elements not in terms of their visual characteristics ("This message should appear in a red box with bold text") but in terms of their function ("This message signals an error", "This text is the page title", "This list of items is a menu of navigation options"). A good front-end framework names its styles according to the function they enable an element to fulfill, rather than according to their visual appearance. - ## SPA or MPA? SPA vs MPA: Are you building something that's more like a website (transactional? lots of different possible screens? user navigates large amounts of data in a typical session? multi-screen workflows?) or more like a desktop app (few screens? continuous interaction vs transactional, eg something like Pivotal Tracker? user navigates modest amounts of data in a typical session? short workflows or interactions typically limited to 1 screen?) If it's primarily a website, use HTML5 + jQuery where needed. If primarily an app, may be better off using a framework. [Need a checklist like "when to use agile" for "when to build a SPA vs MPA"]. Examples of popular SPAs: Gmail, Google Docs, Pivotal Tracker. Popular MPAs: IMDb, Amazon.com, Google Search. *MPA vs SPA is primarily a user experience question, not a technical one!* + --- -**Summary** +<b>Summary</b> -* An _**HTML**_ (HyperText Markup Language) document consists of a hierarchically nested collection of elements. Each element begins with a _**tag**_ in <angle brackets> that may have optional _**attributes**_. Some elements enclose content. -* A _**selector**_ is an expression that identifies one or more HTML elements in a document by using a combination of the element name (such as **body**), element **id** (an element attribute that must be unique on a page), and element **class** (an attribute that need not be unique on a page). -* _**Cascading Style Sheets**_ (CSS) is a stylesheet language describing visual attributes of elements on a Web page. A stylesheet associates sets of visual properties with selectors. A special **link** element inside the **head** element of an HTML document associates a stylesheet with that document. +* An ___HTML___ (HyperText Markup Language) document consists of a hierarchically nested collection of elements. Each element begins with a ___tag___ in <angle brackets> that may have optional ___attributes___. Some elements enclose content. +* A ___selector___ is an expression that identifies one or more HTML elements in a document by using a combination of the element name (such as <b>body</b>), element <b>id</b> (an element attribute that must be unique on a page), and element <b>class</b> (an attribute that need not be unique on a page). +* ___Cascading Style Sheets___ (CSS) is a stylesheet language describing visual attributes of elements on a Web page. A stylesheet associates sets of visual properties with selectors. A special <b>link</b> element inside the <b>head</b> element of an HTML document associates a stylesheet with that document. * The “developer tools” in each browser, such as the Firefox Web Developer toolbar, are invaluable in peeking under the hood to examine both the structure of a page and its stylesheets. @@ -105,10 +107,10 @@ SPA vs MPA: Are you building something that's more like a website (transactional |||challenge +True or false: every HTML element must have an ID. +<p><details><summary>Check yourself</summary> - True or false: every HTML element must have an ID. - - <details><summary>Check yourself</summary>False---the ID is optional, though must be unique if provided.</details> +False---the ID is optional, though must be unique if provided.</details></p> ||| @@ -123,39 +125,34 @@ SPA vs MPA: Are you building something that's more like a website (transactional |||challenge - - - Given the following HTML markup: - - +Given the following HTML markup: **source:ch_arch/code/htmlexercise.html** ```code ch_arch/code/htmlexercise.html ``` + Write down a CSS selector that will select <i>only</i> the word <i>Mondays</i> for styling. +<p><details><summary>Check yourself</summary> - Write down a CSS selector that will select *only* the word - *Mondays* for styling. - <details><summary>Check yourself</summary>Three possibilities, from most specific to least specific, are: **#i span**, **p.x span**, and **.x span**. Other selectors are possible but redundant or over-constrained; for example, **p#i span** and **p#i.x span** are redundant with respect to this HTML snippet since at most one element can have the ID **i**.</details> +Three possibilities, from most specific to least specific, are: <b>#i span</b>, <b>p.x span</b>, and <b>.x span</b>. Other selectors are possible but redundant or over-constrained; for example, <b>p#i span</b> and <b>p#i.x span</b> are redundant with respect to this HTML snippet since at most one element can have the ID <b>i</b>.</details></p> ||| |||challenge +In Self-Check ex:css1, why are <b>span</b> and <b>p span</b> <i>not</i> valid answers? +<p><details><summary>Check yourself</summary> - In Self-Check ex:css1, why are **span** - and **p span** *not* valid answers? - <details><summary>Check yourself</summary>Both of those selector also match *Tuesdays*, which is a **span** inside a **p**.</details> +Both of those selector also match <i>Tuesdays</i>, which is a <b>span</b> inside a <b>p</b>.</details></p> ||| |||challenge +What is the most common way to associate a CSS stylesheet with an HTML or HTML document? (HINT: refer to the earlier screencast example.) +<p><details><summary>Check yourself</summary> - What is the most common way to associate a CSS stylesheet with an HTML - or HTML document? (HINT: refer to the earlier screencast example.) +Within the <b>HEAD</b> element of the HTML or HTML document, include a <b>LINK</b> element with at least the following three attributes: <b>REL="STYLESHEET"</b>, <b>TYPE="text/css"</b>, and <b>HREF="<i>uri</i>"</b>, where <b><i>uri</i></b> is the full or partial URI of the stylesheet. That is, the stylesheet must be accessible as a resource named by a URI.</details></p> - <details><summary>Check yourself</summary>Within the **HEAD** element of the HTML or HTML document, include a **LINK** element with at least the following three attributes: **REL="STYLESHEET"**, **TYPE="text/css"**, and **HREF="*uri*"**, where ***uri*** is the full or partial URI of the stylesheet. That is, the stylesheet must be accessible as a resource named by a URI.</details> - -||| \ No newline at end of file +||| diff --git a/tests/unit/cases/html.tex b/tests/unit/cases/html.tex index b5108b51..d2b7cfe6 100644 --- a/tests/unit/cases/html.tex +++ b/tests/unit/cases/html.tex @@ -64,9 +64,9 @@ \index{Standard Generalized Markup Language (SGML)|textit}% There is an unfortunate and confusing mess of terminology surrounding the -\weblink{http://www.w3.org/TR/html5/introduction.html\#history-1}% -{lineage of HTML}. HTML~5\index{HyperText Markup Language (HTML)!HTML 5 features} -includes features of both its predecessors +\weblink{http://www.w3.org/TR/html5/introduction.html\#history-1}{lineage of HTML} +\index{HyperText Markup Language (HTML)!HTML 5 features} +HTML 5 includes features of both its predecessors (HTML versions 1 through 4) and XHTML\index{eXtended HyperText Markup Language (XHTML)!HTML 5} (eXtended HyperText Markup Language)\index{eXtensible Markup Language (XML)!HTML 5}, which is a subset of \w{XML}, an eXtensible Markup Language diff --git a/tests/unit/cases/index.md b/tests/unit/cases/index.md index 8f8470ed..87fa14b5 100644 --- a/tests/unit/cases/index.md +++ b/tests/unit/cases/index.md @@ -1,2 +1,2 @@ ## Versions of Agile -There is not just a single Agile lifecycle. We are following _**Extreme Programming**_ (XP), which includes one- to two-week iterations, behavior driven design (see Chapter chap:bdd), test-driven development (see Chapter chap:tdd), and pair programming (see Section sec:Pair). Another popular version is _**Scrum**_ (see Section sec:Scrum), where self-organizing teams use two- to four-week iterations called _**sprints**_, and then regroup to plan the next sprint. A key feature is daily standup meetings to identify and overcome obstacles. While there are multiple roles in the scrum team, the norm is to rotate the roles over time. The _**Kanban**_ approach is derived from Toyota's just-in-time manufacturing process, which in this case treats software development as a pipeline. Here the team members have fixed roles, and the goal is to balance the number of team members so that there are no bottlenecks with tasks stacking up waiting for processing. One common feature is a wall of cards that to illustrate the state of all tasks in the pipeline. There are also hybrid lifecycles that try to combine the best of two worlds. For example, _**ScrumBan**_ uses the daily meetings and sprints of Scrum but replaces the planning phase with the more dynamic pipeline control of the wall of cards from Kanban. \ No newline at end of file +There is not just a single Agile lifecycle. We are following ___Extreme Programming___ (XP), which includes one- to two-week iterations, behavior driven design (see Chapter chap:bdd), test-driven development (see Chapter chap:tdd), and pair programming (see Section sec:Pair). Another popular version is ___Scrum___ (see Section sec:Scrum), where self-organizing teams use two- to four-week iterations called ___sprints___, and then regroup to plan the next sprint. A key feature is daily standup meetings to identify and overcome obstacles. While there are multiple roles in the scrum team, the norm is to rotate the roles over time. The ___Kanban___ approach is derived from Toyota's just-in-time manufacturing process, which in this case treats software development as a pipeline. Here the team members have fixed roles, and the goal is to balance the number of team members so that there are no bottlenecks with tasks stacking up waiting for processing. One common feature is a wall of cards that to illustrate the state of all tasks in the pipeline. There are also hybrid lifecycles that try to combine the best of two worlds. For example, ___ScrumBan___ uses the daily meetings and sprints of Scrum but replaces the planning phase with the more dynamic pipeline control of the wall of cards from Kanban. diff --git a/tests/unit/cases/it_bracket.md b/tests/unit/cases/it_bracket.md index bdaf5532..0108bc55 100644 --- a/tests/unit/cases/it_bracket.md +++ b/tests/unit/cases/it_bracket.md @@ -1 +1 @@ -*Think Java* is an introduction to computer science and programming intended for readers with little or no experience. \ No newline at end of file +<i>Think Java</i> is an introduction to computer science and programming intended for readers with little or no experience. \ No newline at end of file diff --git a/tests/unit/cases/italic_bold.md b/tests/unit/cases/italic_bold.md index 15c2ff1d..e4570298 100644 --- a/tests/unit/cases/italic_bold.md +++ b/tests/unit/cases/italic_bold.md @@ -1,3 +1,3 @@ |||info -**Big Design Up Front** , abbreviated _**BDUF**_, is a name some use for software processes like Waterfall, Spiral, and RUP that depend on extensive planning and documentation. They are also known variously as _**heavyweight**_, _**plan-driven**_, _**disciplined**_, or _**structured**_ processes. -||| \ No newline at end of file +**Big Design Up Front** , abbreviated ___BDUF___, is a name some use for software processes like Waterfall, Spiral, and RUP that depend on extensive planning and documentation. They are also known variously as ___heavyweight___, ___plan-driven___, ___disciplined___, or ___structured___ processes. +||| diff --git a/tests/unit/cases/ldots.md b/tests/unit/cases/ldots.md index 3517d6cc..8c865326 100644 --- a/tests/unit/cases/ldots.md +++ b/tests/unit/cases/ldots.md @@ -1 +1 @@ -Beware of overusing *Then I should not see...*. Because it \ No newline at end of file +Beware of overusing <i>Then I should not see...</i>. Because it \ No newline at end of file diff --git a/tests/unit/cases/makequotation.md b/tests/unit/cases/makequotation.md index 2904370f..6c92923b 100644 --- a/tests/unit/cases/makequotation.md +++ b/tests/unit/cases/makequotation.md @@ -1,6 +1,7 @@ > It is a pleasure to see a student text that emphasizes the production of real useful software. I also applaud the emphasis on getting results early in the process. Nothing stimulates student morale and activity more. > -> __Prof. Frederick P. Brooks, Jr., Turing Award winner and author of *The Mythical Man-Month*__ +> __Prof. Frederick P. Brooks, Jr., Turing Award winner and author of <i>The Mythical Man-Month</i>__ + @@ -10,24 +11,28 @@ + > Many congratulations... Very proud of including the SPOC in our redeveloped Bachelors' in Software Engineering degree... The book is the best I've bought, within minutes of seeing it at ICSE. > > __Ali Babar, University of Adelaide__ + > A number of software engineers at C3 Energy consistently report that this book and its companion online course enabled them to rapidly attain proficiency in SaaS development. I recommend this unique book and course to anyone who wants to develop or improve their SaaS programming skills. > > __Thomas M. Siebel, CEO, C3 Energy, and founder and former CEO, Siebel Systems (the leading Customer Relationship Management software company)__ + > We have gone from teaching about 60 graduate students per year in traditional software engineering to 160 per year using ESaaS. We have similarly seen a large increase in undergraduate students. The local community of non-profits has come to depend on us for software development. > > __Prof. Hank Walker, Texas A\&M University__ + > I love this course so much. It's such an amazing advancement in [Software Engineering] education, and I've been so proud to offer it for the past 2 years. It is a big learning curve to new instructors and students, but I truly believe it's worth it. My students seem to agree. > > __Prof. Kristen Walcott-Justice, University of Colorado---Colorado Springs__ @@ -35,6 +40,7 @@ + > A wide and deep coverage of all you need to get started in the SaaS business. > > __Vicente Cuellar, Chief Executive Officer, Wave Crafters, Inc.__ @@ -42,30 +48,35 @@ + > The book filled a gap in my knowledge about cloud computing and the lectures were easy to follow. Perhaps the most exciting part was to write a cloud application, upload, and deploy it to Heroku. > > __Peter Englmaier, University of Zurich, Switzerland__ + > An excellent kickstart into Ruby, Rails and test driven approaches. The fundamentals have been covered with great depth and experience, it's the perfect introduction to modern web development. It should be a requisite for new engineers. > > __Stuart Corbishley, Clue Technologies/CloudSeed, South Africa.__ -> An excellent book that will have you up and running building SaaS apps progressively in a few short days. The screencasts and the GitHub Gists are invaluable. A very practical approach to Agile software development. + +> An excellent book that will have you up and running building SaaS apps progressively in a few short days. The screencasts and the GitHub Gists are invaluable. A very practical approach to Agile software development. > > __Prof. Rakhi Saxena, Assistant Professor, Delhi University, India__ + > The authors have accomplished a very welcome juxtaposition of theory and practice for any modern beginning to advanced Software Engineering course ... I have used the Beta Edition of this book very successfully in my advanced undergraduate software engineering course, where it beautifully complements both my lectures and the team project. > > __Prof. Ingolf Krueger, Professor, University of California at San Diego__ + > A really good introduction book to practical Agile development. All you need is gathered in one book with lots of practical examples. > > __Dmitrij Savicev, Sungard Front Arena, Sweden__ \ No newline at end of file diff --git a/tests/unit/cases/math_runtime.md b/tests/unit/cases/math_runtime.md index 046b3265..3b94c297 100644 --- a/tests/unit/cases/math_runtime.md +++ b/tests/unit/cases/math_runtime.md @@ -6,4 +6,4 @@ On the other hand, if `add` is linear, the total time for $n$ adds would be quad $ runtime = a + b n + c n^2 $ -With perfect data, we might be able to tell the difference between a straight line and a parabola, but if the measurements are noisy, it can be hard to tell. A better way to interpret noisy measurements is to plot runtime and problem size on a **log-log** scale. \ No newline at end of file +With perfect data, we might be able to tell the difference between a straight line and a parabola, but if the measurements are noisy, it can be hard to tell. A better way to interpret noisy measurements is to plot runtime and problem size on a <b>log-log</b> scale. \ No newline at end of file diff --git a/tests/unit/cases/nested_list.md b/tests/unit/cases/nested_list.md index 6a1f83fc..001ae8e8 100644 --- a/tests/unit/cases/nested_list.md +++ b/tests/unit/cases/nested_list.md @@ -1,23 +1,23 @@ -* *Specific.* Here are examples of a vague feature paired with a specific version: +* <i>Specific.</i> Here are examples of a vague feature paired with a specific version: **source:ch_bdd/code/vaguefeature.rb** ```code ch_bdd/code/vaguefeature.rb ``` -* *Measurable.* Adding Measurable to Specific means that each story should be testable, which implies that there are known expected results for some good inputs. An example of a pair of an unmeasurable versus measurable feature is +* <i>Measurable.</i> Adding Measurable to Specific means that each story should be testable, which implies that there are known expected results for some good inputs. An example of a pair of an unmeasurable versus measurable feature is **source:ch_bdd/code/unmeasurablefeature.rb** ```code ch_bdd/code/unmeasurablefeature.rb ``` Only the second case can be tested to see if the system fulfills the requirement. -* *Achievable.* Ideally, you implement the user story in one Agile iteration. If you are getting less than one story per iteration, then they are too big and you need to subdivide these stories into smaller ones. As mentioned above, the tool _**Pivotal Tracker**_ measures _**Velocity**_, which is the rate of completing stories of varying difficulty. -* *Relevant.* A user story must have business value to one or more stakeholders. To drill down to the real business value, one technique is to keep asking “Why.” Using as an example a ticket-selling app for a regional theater, suppose the proposal is to add a Facebook linking feature. Here are the “Five Whys” in action with their recursive questions and answers: +* <i>Achievable.</i> Ideally, you implement the user story in one Agile iteration. If you are getting less than one story per iteration, then they are too big and you need to subdivide these stories into smaller ones. As mentioned above, the tool ___Pivotal Tracker___ measures ___Velocity___, which is the rate of completing stories of varying difficulty. +* <i>Relevant.</i> A user story must have business value to one or more stakeholders. To drill down to the real business value, one technique is to keep asking “Why.” Using as an example a ticket-selling app for a regional theater, suppose the proposal is to add a Facebook linking feature. Here are the “Five Whys” in action with their recursive questions and answers: 1. Why add the Facebook feature? As box office manager, I think more people will go with friends and enjoy the show more. -1. Why does it matter if they enjoy the show more? I think we will sell more tickets. -1. Why do you want to sell more tickets? Because then the theater makes more money. -1. Why does theater want to make more money? We want to make more money so that we don't go out of business. -1. Why does it matter that theater is in business next year? If not, I have no job. +2. Why does it matter if they enjoy the show more? I think we will sell more tickets. +3. Why do you want to sell more tickets? Because then the theater makes more money. +4. Why does theater want to make more money? We want to make more money so that we don't go out of business. +5. Why does it matter that theater is in business next year? If not, I have no job. (We're pretty sure the business value is now apparent to at least one stakeholder!) -* *Timeboxed.* Timeboxing means that you stop developing a story once you've exceeded the time budget. Either you give up, divide the user story into smaller ones, or reschedule what is left according to a new estimate. If dividing looks like it won't help, then you go back to the customers to find the highest value part of the story that you can do quickly. The reason for a time budget per user story is that it is extremely easy to underestimate the length of a software project. Without careful accounting of each iteration, the whole project could be late, and thus fail. Learning to budget a software project is a critical skill, and exceeding a story budget and then refactoring it is one way to acquire that skill. \ No newline at end of file +* <i>Timeboxed.</i> Timeboxing means that you stop developing a story once you've exceeded the time budget. Either you give up, divide the user story into smaller ones, or reschedule what is left according to a new estimate. If dividing looks like it won't help, then you go back to the customers to find the highest value part of the story that you can do quickly. The reason for a time budget per user story is that it is extremely easy to underestimate the length of a software project. Without careful accounting of each iteration, the whole project could be late, and thus fail. Learning to budget a software project is a critical skill, and exceeding a story budget and then refactoring it is one way to acquire that skill. diff --git a/tests/unit/cases/new_line.md b/tests/unit/cases/new_line.md index f40a25f8..c09cf8d6 100644 --- a/tests/unit/cases/new_line.md +++ b/tests/unit/cases/new_line.md @@ -1,8 +1,4 @@ -> No bottles of beer on the wall,<br/> -> no bottles of beer,<br/> -> ya' can't take one down, ya' can't pass it around,<br/> -> 'cause there are no more bottles of beer on the wall! - +> No bottles of beer on the wall,<br/> no bottles of beer,<br/> ya' can't take one down, ya' can't pass it around,<br/> 'cause there are no more bottles of beer on the wall! diff --git a/tests/unit/cases/picfigure.md b/tests/unit/cases/picfigure.md index af8e79d1..d45825ae 100644 --- a/tests/unit/cases/picfigure.md +++ b/tests/unit/cases/picfigure.md @@ -1,7 +1,6 @@ ![Screen image of the UI of the Pivotal Tracker service.](ch_teams/figs/Tracker.jpg) -**Figure 1.1** -Screen image of the UI of the Pivotal Tracker service. +**<p style="font-size: 10px">Figure 1.1: Screen image of the UI of the Pivotal Tracker service.</p>** + ![Storyboard of UI for searching The Movie Database.](ch_bdd/figs/SearchTMDbStoryBoard.jpg) -**Figure 1.2** -Storyboard of UI for searching The Movie Database. \ No newline at end of file +**<p style="font-size: 10px">Figure 1.2: Storyboard of UI for searching The Movie Database.</p>** \ No newline at end of file diff --git a/tests/unit/cases/pitfall.md b/tests/unit/cases/pitfall.md index 04b8105a..f94ea463 100644 --- a/tests/unit/cases/pitfall.md +++ b/tests/unit/cases/pitfall.md @@ -1,2 +1,2 @@ ## Customers who confuse mock-ups with completed features. -As a developer, this pitfall may seem ridiculous to you. But nontechnical customers sometimes have difficulty distinguishing a highly polished digital mock-up from a working feature! The solution is simple: use paper-and-pencil techniques such as hand-drawn sketches and storyboards to reach agreement with the customer---there can be no doubt that such Lo-Fi mockups represent *proposed* rather than implemented functionality. \ No newline at end of file +As a developer, this pitfall may seem ridiculous to you. But nontechnical customers sometimes have difficulty distinguishing a highly polished digital mock-up from a working feature! The solution is simple: use paper-and-pencil techniques such as hand-drawn sketches and storyboards to reach agreement with the customer---there can be no doubt that such Lo-Fi mockups represent <i>proposed</i> rather than implemented functionality. \ No newline at end of file diff --git a/tests/unit/cases/quote.md b/tests/unit/cases/quote.md index 7792643b..3f38c2b6 100644 --- a/tests/unit/cases/quote.md +++ b/tests/unit/cases/quote.md @@ -1,3 +1 @@ -> A word is said to be a “doubloon” if every letter that appears in the word appears exactly twice. -> Write a method called `isDoubloon` that takes a string and checks whether it is a doubloon. -> To ignore case, invoke the `toLowerCase` method before checking. \ No newline at end of file +> A word is said to be a “doubloon” if every letter that appears in the word appears exactly twice. Write a method called `isDoubloon` that takes a string and checks whether it is a doubloon. To ignore case, invoke the `toLowerCase` method before checking. \ No newline at end of file diff --git a/tests/unit/cases/saas.md b/tests/unit/cases/saas.md index ee2a3a56..784b91ce 100644 --- a/tests/unit/cases/saas.md +++ b/tests/unit/cases/saas.md @@ -1,23 +1,23 @@ ### Software as a Service -The power of SOA combined with the power of the Internet led to a special case of SOA with its own name: _**Software as a Service (SaaS)**_. It delivers software and data as a service over the Internet, usually via a thin program such as a browser that runs on local client devices instead as an application binary that must be installed and runs wholly on that device. Examples that many use every day include searching, social networking, and watching videos. The advantages for the customer and for the software developer are widely touted: +The power of SOA combined with the power of the Internet led to a special case of SOA with its own name: ___Software as a Service (SaaS)___. It delivers software and data as a service over the Internet, usually via a thin program such as a browser that runs on local client devices instead as an application binary that must be installed and runs wholly on that device. Examples that many use every day include searching, social networking, and watching videos. The advantages for the customer and for the software developer are widely touted: 1. Since customers do not need to install the application, they don't have to worry whether their hardware is the right brand or fast enough, nor whether they have the correct version of the operating system. -1. The data associated with the service is generally kept with the service, so customers need not worry about backing it up, losing it due to a local hardware malfunction, or even losing the whole device, such as a phone or tablet. -1. When a group of users wants to collectively interact with the same data, SaaS is a natural vehicle. -1. When data is large and/or updated frequently, it may make more sense to centralize data and offer remote access via SaaS. -1. Only a single copy of the server software runs in a uniform, tightly-controlled hardware and operating system environment selected by the developer, which avoids the compatibility hassles of distributing binaries that must run on wide-ranging computers and operating systems. In addition, developers can test new versions of the application on a small fraction of the real customers temporarily without disturbing most customers. (If the SaaS client runs in a browser, there still are compatibility challenges, which we describe in Chapter chap:arch.) +2. The data associated with the service is generally kept with the service, so customers need not worry about backing it up, losing it due to a local hardware malfunction, or even losing the whole device, such as a phone or tablet. +3. When a group of users wants to collectively interact with the same data, SaaS is a natural vehicle. +4. When data is large and/or updated frequently, it may make more sense to centralize data and offer remote access via SaaS. +5. Only a single copy of the server software runs in a uniform, tightly-controlled hardware and operating system environment selected by the developer, which avoids the compatibility hassles of distributing binaries that must run on wide-ranging computers and operating systems. In addition, developers can test new versions of the application on a small fraction of the real customers temporarily without disturbing most customers. (If the SaaS client runs in a browser, there still are compatibility challenges, which we describe in Chapter chap:arch.) |||info **SaaS: Innovate or Die?** Lest you think the perceived need to improve a successful service is just software engineering paranoia, the most popular search engine used to be AltaVista and the most popular social networking site used to be MySpace. ||| -1. SaaS companies compete regularly on bringing out new features to help ensure that their customers do not abandon them for a competitor who offers a better service. -1. Since only developers have a copy of the software, they can upgrade the software and underlying hardware frequently as long as they don't violate the external application program interfaces (API). Moreover, developers don't need to annoy users with the seemingly endless requests for permission to upgrade their applications. +6. SaaS companies compete regularly on bringing out new features to help ensure that their customers do not abandon them for a competitor who offers a better service. +7. Since only developers have a copy of the software, they can upgrade the software and underlying hardware frequently as long as they don't violate the external application program interfaces (API). Moreover, developers don't need to annoy users with the seemingly endless requests for permission to upgrade their applications. @@ -25,7 +25,7 @@ Combining the advantages to the customer and the developer together explains why file content -**Figure 1.1: Examples of SaaS programming frameworks and the programming languages they are written in** +**<p style="font-size: 10px">Figure 1.1: Examples of SaaS programming frameworks and the programming languages they are written in</p>** @@ -34,7 +34,7 @@ Unsurprisingly, given the popularity of SaaS, Figure fig:SaaS_frameworks lists t Ruby is typical of modern scripting languages in including automatic memory management and dynamic typing. By including important advances in programming languages, Ruby goes beyond languages like Perl in supporting multiple programming paradigms such as object oriented and functional programming. -Useful additional features that help productivity via reuse include _**mix-ins**_, which collect related behaviors and make it easy to add them to many different classes, and _**metaprogramming**_, which allows Ruby programs to synthesize code at runtime. Reuse is also enhanced with Ruby's support for _**closures**_ via _**blocks**_ and _**yield**_. Chapter chap:ruby_intro is a short description of Ruby for those who already know Java, and Chapter chap:rails_intro introduces Rails. +Useful additional features that help productivity via reuse include ___mix-ins___, which collect related behaviors and make it easy to add them to many different classes, and ___metaprogramming___, which allows Ruby programs to synthesize code at runtime. Reuse is also enhanced with Ruby's support for ___closures___ via ___blocks___ and ___yield___. Chapter chap:ruby_intro is a short description of Ruby for those who already know Java, and Chapter chap:rails_intro introduces Rails. In addition to our view of Rails being technically superior for Agile and SaaS, Ruby and Rails are widely used. For example, Ruby routinely appears among top 10 most popular programming languages. A well-known SaaS app associated with Rails is Twitter, which began as a Rails app in 2006 and grew from 20,000 tweets per day in 2007 to 200,000,000 in 2011, during which time other frameworks replaced various parts of it. @@ -44,37 +44,38 @@ Note that frequent upgrades of SaaS---due to only having a single copy of the so --- -**Summary:** _**Software as a Service (SaaS)**_ is attractive to both customers and providers because the universal client (the Web browser) makes it easier for customers to use the service and the single version of the software at a centralized site makes it easier for the provider to deliver and improve the service. Given the ability and desire to frequently upgrade SaaS, the Agile software development process is popular for SaaS, and so there are many frameworks to support Agile and SaaS. This book uses Ruby on Rails. +<b>Summary:</b> ___Software as a Service (SaaS)___ is attractive to both customers and providers because the universal client (the Web browser) makes it easier for customers to use the service and the single version of the software at a centralized site makes it easier for the provider to deliver and improve the service. Given the ability and desire to frequently upgrade SaaS, the Agile software development process is popular for SaaS, and so there are many frameworks to support Agile and SaaS. This book uses Ruby on Rails. --- |||challenge +Which of the examples of Google SaaS apps---Search, Maps, News, Gmail, Calendar, YouTube, and Documents---is the <i>best</i> match to each of the six arguments given in this section for SaaS, reproduced below. +<p><details><summary>Check yourself</summary> -Which of the examples of Google SaaS apps---Search, Maps, News, Gmail, Calendar, YouTube, and Documents---is the *best* match to each of the six arguments given in this section for SaaS, reproduced below. - <details><summary>Check yourself</summary>While you can argue the mappings, below is our answer. (Note that we cheated and put some apps in multiple categories) - +While you can argue the mappings, below is our answer. (Note that we cheated and put some apps in multiple categories) 1. No user installation: Documents -1. Can't lose data: Gmail, Calendar. -1. Users cooperating: Documents. -1. Large/changing datasets: Search, Maps, News, and YouTube. -1. Software centralized in single environment: Search. -1. No field upgrades when improve app: Documents. +2. Can't lose data: Gmail, Calendar. +3. Users cooperating: Documents. +4. Large/changing datasets: Search, Maps, News, and YouTube. +5. Software centralized in single environment: Search. +6. No field upgrades when improve app: Documents. -</details> +</details></p> ||| |||challenge +True or False: If you are using the Agile development process to develop SaaS apps, you could use Python and Django or languages based on the Microsoft's .NET framework and ASP.NET instead of Ruby and Rails. +<p><details><summary>Check yourself</summary> - True or False: If you are using the Agile development process to develop SaaS apps, you could use Python and Django or languages based on the Microsoft's .NET framework and ASP.NET instead of Ruby and Rails. - <details><summary>Check yourself</summary>True. Programming frameworks for Agile and SaaS include Django and ASP.NET.</details> +True. Programming frameworks for Agile and SaaS include Django and ASP.NET.</details></p> ||| -Given the case for SaaS and the understanding that it relies on a Service Oriented Architecture, we are ready to see the underlying hardware that makes SaaS possible. \ No newline at end of file +Given the case for SaaS and the understanding that it relies on a Service Oriented Architecture, we are ready to see the underlying hardware that makes SaaS possible. diff --git a/tests/unit/cases/saas1.md b/tests/unit/cases/saas1.md index d8638357..2455a338 100644 --- a/tests/unit/cases/saas1.md +++ b/tests/unit/cases/saas1.md @@ -1,25 +1,26 @@ ### Software Development Processes: Plan and Document - -> If builders built buildings the way programmers wrote programs, then the first woodpecker that came along would destroy civilization.<br/> +> If builders built buildings the way programmers wrote programs, then the first woodpecker that came along would destroy civilization. > -> __Gerald Weinberg, *Weinberg's Second Law*__ +> __Gerald Weinberg, <i>Weinberg's Second Law</i>__ -The general unpredictability of software development in the late 1960s, along with the software disasters similar to ACA, led to the study of how high-quality software could be developed on a predictable schedule and budget. Drawing the analogy to other engineering fields, the term _**software engineering**_ was coined (Naur69). The goal was to discover methods to build software that were as predictable in quality, cost, and time as those used to build bridges in civil engineering. +The general unpredictability of software development in the late 1960s, along with the software disasters similar to ACA, led to the study of how high-quality software could be developed on a predictable schedule and budget. Drawing the analogy to other engineering fields, the term ___software engineering___ was coined (Naur69). The goal was to discover methods to build software that were as predictable in quality, cost, and time as those used to build bridges in civil engineering. One thrust of software engineering was to bring an engineering discipline to what was often unplanned software development. Before starting to code, come up with a plan for the project, including extensive, detailed documentation of all phases of that plan. Progress is then measured against the plan. Changes to the project must be reflected in the documentation and possibly to the plan. The goal of all these “Plan-and-Document” software development processes is to improve predictability via extensive documentation, which must be changed whenever the goals change. Here is how textbook authors put it (Lethbridge02,Braude01): + > Documentation should be written at all stages of development, and includes requirements, designs, user manuals, instructions for testers and project plans. > > __Timothy Lethbridge and Robert Laganiere, 2002__ + > Documentation is the lifeblood of software engineering. > > __Eric Braude, 2001__ @@ -44,16 +45,16 @@ An early version of this Plan-and-Document software development process was deve 1. Requirements analysis and specification -1. Architectural design -1. Implementation and Integration -1. Verification -1. Operation and Maintenance +2. Architectural design +3. Implementation and Integration +4. Verification +5. Operation and Maintenance Given that the earlier you find an error the cheaper it is to fix, the philosophy of this process is to complete a phase before going on to the next one, thereby removing as many errors as early as possible. Getting the early phases right could also prevent unnecessary work downstream. As this process could take years, the extensive documentation helps to ensure that important information is not lost if a person leaves the project and that new people can get up to speed quickly when they join the project. -Because it flows from the top down to completion, this process is called the _**Waterfall**_ software development process or Waterfall software development _**lifecycle**_. Understandably, given the complexity of each stage in the Waterfall lifecycle, product releases are major events toward which engineers worked feverishly and which are accompanied by much fanfare. +Because it flows from the top down to completion, this process is called the ___Waterfall___ software development process or Waterfall software development ___lifecycle___. Understandably, given the complexity of each stage in the Waterfall lifecycle, product releases are major events toward which engineers worked feverishly and which are accompanied by much fanfare. |||info @@ -66,6 +67,7 @@ In the Waterfall lifecycle, the long life of software is acknowledged by a maint The Waterfall model can work well with well-specified tasks like NASA space flights, but it runs into trouble when customers change their minds about what they want. A Turing Award winner captures this observation: + > Plan to throw one [implementation] away; you will, anyhow. > > __Fred Brooks, Jr.__ @@ -74,39 +76,39 @@ The Waterfall model can work well with well-specified tasks like NASA space flig That is, it's easier for customers to understand what they want once they see a prototype and for engineers to understand how to build it better once they've done it the first time. -This observation led to a software development lifecycle developed in the 1980s that combines prototypes with the Waterfall model (boehm86). The idea is to iterate through a sequence of four phases, with each iteration resulting in a prototype that is a refinement of the previous version. Figure fig:spiral illustrates this model of development across the four phases, which gives this lifecycle its name: the _**Spiral model**_. The phases are +This observation led to a software development lifecycle developed in the 1980s that combines prototypes with the Waterfall model (boehm86). The idea is to iterate through a sequence of four phases, with each iteration resulting in a prototype that is a refinement of the previous version. Figure fig:spiral illustrates this model of development across the four phases, which gives this lifecycle its name: the ___Spiral model___. The phases are 1. Determine objectives and constraints of this iteration -1. Evaluate alternatives and identify and resolve risks -1. Develop and verify the prototype for this iteration -1. Plan the next iteration +2. Evaluate alternatives and identify and resolve risks +3. Develop and verify the prototype for this iteration +4. Plan the next iteration ![The Spiral lifecycle combines Waterfall with prototyping. It starts at the center, with each iteration around the spiral going through the four phases and resulting in a revised prototype until the product is ready for release.](ch_intro/figs/Spiral.jpg) -**Figure 1.1** -The Spiral lifecycle combines Waterfall with prototyping. It starts at the center, with each iteration around the spiral going through the four phases and resulting in a revised prototype until the product is ready for release. +**<p style="font-size: 10px">Figure 1.1: The Spiral lifecycle combines Waterfall with prototyping. It starts at the center, with each iteration around the spiral going through the four phases and resulting in a revised prototype until the product is ready for release.</p>** + Rather than document all the requirements at the beginning, as in the Waterfall model, the requirement documents are developed across the iteration as they are needed and evolve with the project. Iterations involve the customer before the product is completed, which reduces chances of misunderstandings. However, as originally envisioned, these iterations were 6 to 24 months long, so there is plenty of time for customers to change their minds during an iteration! Thus, Spiral still relies on planning and extensive documentation, but the plan is expected to evolve on each iteration. |||info -**Big Design Up Front** , abbreviated _**BDUF**_, is a name some use for software processes like Waterfall, Spiral, and RUP that depend on extensive planning and documentation. They are also known variously as _**heavyweight**_, _**plan-driven**_, _**disciplined**_, or _**structured**_ processes. +**Big Design Up Front** , abbreviated ___BDUF___, is a name some use for software processes like Waterfall, Spiral, and RUP that depend on extensive planning and documentation. They are also known variously as ___heavyweight___, ___plan-driven___, ___disciplined___, or ___structured___ processes. ||| -Given the importance of software development, many variations of Plan-and-Document methodologies were proposed beyond these two. A recent one is called the _**Rational Unified Process**_ (_**RUP**_) (Kruchten03), which combines features of both Waterfall and Spiral lifecycles as well standards for diagrams and documentation. We'll use RUP as a representative of the latest thinking in Plan-and-Document lifecycles. Unlike Waterfall and Spiral, it is more closely allied to business issues than to technical issues. +Given the importance of software development, many variations of Plan-and-Document methodologies were proposed beyond these two. A recent one is called the ___Rational Unified Process___ (___RUP___) (Kruchten03), which combines features of both Waterfall and Spiral lifecycles as well standards for diagrams and documentation. We'll use RUP as a representative of the latest thinking in Plan-and-Document lifecycles. Unlike Waterfall and Spiral, it is more closely allied to business issues than to technical issues. Like Waterfall and Spiral, RUP has phases: 1. Inception: makes the business case for the software and scopes the project to set the schedule and budget, which is used to judge progress and justify expenditures, and initial assessment of risks to schedule and budget. -1. Elaboration: works with stakeholders to identify use cases, designs a software architecture, sets the development plan, and builds an initial prototype. -1. Construction: codes and tests the product, resulting in the first external release. -1. Transition: moves the product from development to production in the real environment, including customer acceptance testing and user training. +2. Elaboration: works with stakeholders to identify use cases, designs a software architecture, sets the development plan, and builds an initial prototype. +3. Construction: codes and tests the product, resulting in the first external release. +4. Transition: moves the product from development to production in the real environment, including customer acceptance testing and user training. @@ -117,57 +119,58 @@ In addition to the dynamically changing phases of the project, RUP identifies si 1. Business Modeling -1. Requirements -1. Analysis and Design -1. Implementation -1. Test -1. Deployment +2. Requirements +3. Analysis and Design +4. Implementation +5. Test +6. Deployment These disciplines are more static than the phases, in that they nominally exist over the whole lifetime of the project. However, some disciplines get used more in earlier phases (like business modeling), some periodically throughout the process (like test), and some more towards the end (deployment). Figure fig:RUP shows the relationship of the phases and the disciplines, with the area indicating the amount of effort in each discipline over time. ![The Rational Unified Process lifecycle allows the project to have multiple iterations in each phase and identifies the skills needed by the project team, which vary in effort over time. RUP also has three “supporting disciplines” not shown in this figure: Configuration and Change Management, Project Management, and Environment. (Image from Wikipedia Commons by Dutchgilder.)](ch_intro/figs/RUP.jpg) -**Figure 1.2** -The Rational Unified Process lifecycle allows the project to have multiple iterations in each phase and identifies the skills needed by the project team, which vary in effort over time. RUP also has three “supporting disciplines” not shown in this figure: Configuration and Change Management, Project Management, and Environment. (Image from Wikipedia Commons by Dutchgilder.) +**<p style="font-size: 10px">Figure 1.2: The Rational Unified Process lifecycle allows the project to have multiple iterations in each phase and identifies the skills needed by the project team, which vary in effort over time. RUP also has three “supporting disciplines” not shown in this figure: Configuration and Change Management, Project Management, and Environment. (Image from Wikipedia Commons by Dutchgilder.)</p>** + An unfortunate downside to teaching a Plan-and-Document approach is that students may find software development tedious (Nawrocki02,Estler12). Given the importance of predictable software development, this is hardly a strong enough reason not to teach it; the good news is that there are alternatives that work just as well for many projects that are a better fit to the classroom, as we describe in the next section. --- -**Summary:** The basic *activities* of software engineering are the same in all the software development process or _**lifecycles**_, but their interaction over time relative to product releases differs among the models. The Waterfall lifecycle is characterized by much of the design being done in advance of coding, completing each phase before going on to the next one. The Spiral lifecycle iterates through all the development phases to produce prototypes, but like Waterfall, the customers may only get involved every 6 to 24 months. The more recent Rational Unified Process lifecycle includes phases, iterations, and prototypes, while identifying the people skills needed for the project. All rely on careful planning and thorough documentation, and all measure progress against a plan. +<b>Summary:</b> The basic <i>activities</i> of software engineering are the same in all the software development process or ___lifecycles___, but their interaction over time relative to product releases differs among the models. The Waterfall lifecycle is characterized by much of the design being done in advance of coding, completing each phase before going on to the next one. The Spiral lifecycle iterates through all the development phases to produce prototypes, but like Waterfall, the customers may only get involved every 6 to 24 months. The more recent Rational Unified Process lifecycle includes phases, iterations, and prototypes, while identifying the people skills needed for the project. All rely on careful planning and thorough documentation, and all measure progress against a plan. --- |||challenge +What are a major similarity and a major difference between processes like Spiral and RUP versus Waterfall? +<p><details><summary>Check yourself</summary> - What are a major similarity and a major difference between processes like Spiral and RUP versus Waterfall? - <details><summary>Check yourself</summary>All rely on planning and documentation, but Spiral and RUP use iteration and prototypes to improve them over time versus a single long path to the product.</details> +All rely on planning and documentation, but Spiral and RUP use iteration and prototypes to improve them over time versus a single long path to the product.</details></p> ||| |||challenge +What are the differences between the phases of these Plan-and-Document processes? +<p><details><summary>Check yourself</summary> - What are the differences between the phases of these Plan-and-Document processes? - <details><summary>Check yourself</summary>Waterfall phases separate planning (requirements and architectural design) from implementation. Testing the product before release is next, followed by a separate operations phase. The Spiral phases are aimed at an iteration: set the goals for an iteration; explore alternatives; develop and verify the prototype for this iteration; and plan the next iteration. RUP phases are tied closer to business objectives: inception makes business case and sets schedule and budget; elaboration works with customers to build an initial prototype; construction builds and test the first version; and transition deploys the product.</details> +Waterfall phases separate planning (requirements and architectural design) from implementation. Testing the product before release is next, followed by a separate operations phase. The Spiral phases are aimed at an iteration: set the goals for an iteration; explore alternatives; develop and verify the prototype for this iteration; and plan the next iteration. RUP phases are tied closer to business objectives: inception makes business case and sets schedule and budget; elaboration works with customers to build an initial prototype; construction builds and test the first version; and transition deploys the product.</details></p> ||| - ## SEI Capability Maturity Model (CMM) -The Software Engineering Institute at Carnegie Mellon University proposed the _**Capability Maturity Model**_ (CMM) (Paulk05) to evaluate organizations' software-development processes based on Plan-and-Document methodologies. The idea is that by modeling the software development process, an organization can improve them. SEI studies observed five levels of software practice: +The Software Engineering Institute at Carnegie Mellon University proposed the ___Capability Maturity Model___ (CMM) (Paulk05) to evaluate organizations' software-development processes based on Plan-and-Document methodologies. The idea is that by modeling the software development process, an organization can improve them. SEI studies observed five levels of software practice: -1. Initial or Chaotic---undocumented/*ad hoc*/unstable software development. -1. Repeatable---not following rigorous discipline, but some processes repeatable with consistent results. -1. Defined---Defined and documented standard processes that improve over time. -1. Managed---Management can control software development using process metrics, adapting the process to different projects successfully. -1. Optimizing---Deliberate process optimization improvements as part of management process. +1. Initial or Chaotic---undocumented/<i>ad hoc</i>/unstable software development. +2. Repeatable---not following rigorous discipline, but some processes repeatable with consistent results. +3. Defined---Defined and documented standard processes that improve over time. +4. Managed---Management can control software development using process metrics, adapting the process to different projects successfully. +5. Optimizing---Deliberate process optimization improvements as part of management process. -CMM implicitly encourages an organization to move up the CMM levels. While not proposed as a software development methodology, many consider it one. For example, (Nawrocki02) compares CMM Level 2 to the Agile software methodology (see next section). \ No newline at end of file +CMM implicitly encourages an organization to move up the CMM levels. While not proposed as a software development methodology, many consider it one. For example, (Nawrocki02) compares CMM Level 2 to the Agile software methodology (see next section). diff --git a/tests/unit/cases/saas1.tex b/tests/unit/cases/saas1.tex index 4d435b1f..87e94954 100644 --- a/tests/unit/cases/saas1.tex +++ b/tests/unit/cases/saas1.tex @@ -4,7 +4,7 @@ \section{Software Development Processes: Plan and Document} \index{Plan-and-Document!overview|textbf}% \index{Software as a Service (SaaS)!plan-and-document|textbf}% -\makequotation{If builders built buildings the way programmers wrote programs, then the first woodpecker that came along would destroy civilization.\\}{Gerald Weinberg, \emph{Weinberg's Second Law}} +\makequotation{If builders built buildings the way programmers wrote programs, then the first woodpecker that came along would destroy civilization.}{Gerald Weinberg, \emph{Weinberg's Second Law}} The general unpredictability of software development in the late 1960s, along with the software disasters similar to ACA, led to the study of diff --git a/tests/unit/cases/saas2.md b/tests/unit/cases/saas2.md index 03a76a01..dc6a5d47 100644 --- a/tests/unit/cases/saas2.md +++ b/tests/unit/cases/saas2.md @@ -3,24 +3,22 @@ Thus, we concentrate on Agile in the six software development chapters in Part I While we now see how to build some software successfully, not all projects are small. We next show how to design software to enable composition into services like Amazon.com. --- -**Summary:** In contrast to the Plan-and-Document lifecycles, the Agile lifecycle works with customers to continuously add features to working prototypes until the customer is satisfied, allowing customers to change what they want as the project develops. Documentation is primarily through user stories and test cases, and it does not measure progress against a predefined plan. Progress is gauged instead by recording _**velocity**_, which essentially is the rate that a project completes features. +<b>Summary:</b> In contrast to the Plan-and-Document lifecycles, the Agile lifecycle works with customers to continuously add features to working prototypes until the customer is satisfied, allowing customers to change what they want as the project develops. Documentation is primarily through user stories and test cases, and it does not measure progress against a predefined plan. Progress is gauged instead by recording ___velocity___, which essentially is the rate that a project completes features. --- |||challenge +True or False: A big difference between Spiral and Agile development is building prototypes and interacting with customers during the process. +<p><details><summary>Check yourself</summary> - True or False: A big difference between Spiral and Agile development is building prototypes and interacting with customers during the process. - <details><summary>Check yourself</summary>False: Both build working but incomplete prototypes that the customer helps evaluate. The difference is that customers are involved every two weeks in Agile versus up to two years in with Spiral.</details> +False: Both build working but incomplete prototypes that the customer helps evaluate. The difference is that customers are involved every two weeks in Agile versus up to two years in with Spiral.</details></p> ||| - - - ## Versions of Agile -There is not just a single Agile lifecycle. We are following _**Extreme Programming**_ (XP), which includes one- to two-week iterations, behavior driven design (see Chapter chap:bdd), test-driven development (see Chapter chap:tdd), and pair programming (see Section sec:Pair). Another popular version is _**Scrum**_ (see Section sec:Scrum), where self-organizing teams use two- to four-week iterations called _**sprints**_, and then regroup to plan the next sprint. A key feature is daily standup meetings to identify and overcome obstacles. While there are multiple roles in the scrum team, the norm is to rotate the roles over time. The _**Kanban**_ approach is derived from Toyota's just-in-time manufacturing process, which in this case treats software development as a pipeline. Here the team members have fixed roles, and the goal is to balance the number of team members so that there are no bottlenecks with tasks stacking up waiting for processing. One common feature is a wall of cards that to illustrate the state of all tasks in the pipeline. There are also hybrid lifecycles that try to combine the best of two worlds. For example, _**ScrumBan**_ uses the daily meetings and sprints of Scrum but replaces the planning phase with the more dynamic pipeline control of the wall of cards from Kanban. +There is not just a single Agile lifecycle. We are following ___Extreme Programming___ (XP), which includes one- to two-week iterations, behavior driven design (see Chapter chap:bdd), test-driven development (see Chapter chap:tdd), and pair programming (see Section sec:Pair). Another popular version is ___Scrum___ (see Section sec:Scrum), where self-organizing teams use two- to four-week iterations called ___sprints___, and then regroup to plan the next sprint. A key feature is daily standup meetings to identify and overcome obstacles. While there are multiple roles in the scrum team, the norm is to rotate the roles over time. The ___Kanban___ approach is derived from Toyota's just-in-time manufacturing process, which in this case treats software development as a pipeline. Here the team members have fixed roles, and the goal is to balance the number of team members so that there are no bottlenecks with tasks stacking up waiting for processing. One common feature is a wall of cards that to illustrate the state of all tasks in the pipeline. There are also hybrid lifecycles that try to combine the best of two worlds. For example, ___ScrumBan___ uses the daily meetings and sprints of Scrum but replaces the planning phase with the more dynamic pipeline control of the wall of cards from Kanban. ## Reforming Acquisition Regulations Long before the ACA website, there were calls to reform software acquisition, as in this US National Academies study of the Department of Defense (DOD): @@ -29,4 +27,4 @@ Long before the ACA website, there were calls to reform software acquisition, as (national2010Achieving) -Even President Obama belatedly recognized the difficulties of software acquisition. On November 14, 2013, he said in a speech: “... when I do some Monday morning quarterbacking on myself, one of the things that I do recognize is since I know how we purchase technology in the federal government is cumbersome, complicated and outdated ... it's part of the reason why, chronically, federal IT programs are over budget, behind schedule... since I [now] know that the federal government has not been good at this stuff in the past, two years ago as we were thinking about this... we might have done more to make sure that we were breaking the mold on how we were going to be setting this up.” \ No newline at end of file +Even President Obama belatedly recognized the difficulties of software acquisition. On November 14, 2013, he said in a speech: “... when I do some Monday morning quarterbacking on myself, one of the things that I do recognize is since I know how we purchase technology in the federal government is cumbersome, complicated and outdated ... it's part of the reason why, chronically, federal IT programs are over budget, behind schedule... since I [now] know that the federal government has not been good at this stuff in the past, two years ago as we were thinking about this... we might have done more to make sure that we were breaking the mold on how we were going to be setting this up.” diff --git a/tests/unit/cases/saas3.md b/tests/unit/cases/saas3.md index 91a31601..e47f23de 100644 --- a/tests/unit/cases/saas3.md +++ b/tests/unit/cases/saas3.md @@ -1,4 +1,4 @@ -> To me programming is more than an important practical art. It is also a gigantic undertaking in the foundations of knowledge.<br/> +> To me programming is more than an important practical art. It is also a gigantic undertaking in the foundations of knowledge. > > __Grace Murray Hopper__ @@ -8,7 +8,7 @@ |||xdiscipline **Grace Murray Hopper** (1906--1992) was one of the first programmers, developed the first compiler, and was referred to as “Amazing Grace.” She became a Rear Admiral in the US Navy, and in 1997, a warship was named for her: the USS Hopper. -![Grace Murray Hopper](ch_backwards_forwards/figs/Hopper.png) +<img alt='Grace Murray Hopper' src='ch_backwards_forwards/figs/Hopper.png' style='width:200px' /> ||| diff --git a/tests/unit/cases/saas3.tex b/tests/unit/cases/saas3.tex index 985d0d6e..021b5655 100644 --- a/tests/unit/cases/saas3.tex +++ b/tests/unit/cases/saas3.tex @@ -1,5 +1,5 @@ \makequotation{ -To me programming is more than an important practical art. It is also a gigantic undertaking in the foundations of knowledge.\\}{Grace Murray Hopper} +To me programming is more than an important practical art. It is also a gigantic undertaking in the foundations of knowledge.}{Grace Murray Hopper} \index{Software as a Service (SaaS)!beautiful \textit{vs.} legacy code|textbf}% \begin{sidebargraphic}[-1in]{ch_backwards_forwards/figs/Hopper}{Grace Murray Hopper} diff --git a/tests/unit/cases/screencast.md b/tests/unit/cases/screencast.md index a660c6fc..07d4c671 100644 --- a/tests/unit/cases/screencast.md +++ b/tests/unit/cases/screencast.md @@ -5,7 +5,7 @@ <iframe width="560" height="315" src="//www.youtube.com/embed/X5ArSbUea_o" frameborder="0" allowfullscreen></iframe> -CSS uses _**selector notations**_ such as **div#***name* to indicate a **div** element whose **id** is *name* and **div.***name* to indicate a **div** element with class *name*. Only one element in an HTML document can have a given **id**, whereas many elements (even of different tag types) can share the same **class**. All three aspects of an element---its tag type, its **id** (if it has one), and its **class** attributes (if it has any)---can be used to identify an element as a candidate for visual formatting. +CSS uses ___selector notations___ such as <b>div#</b><i>name</i> to indicate a <b>div</b> element whose <b>id</b> is <i>name</i> and <b>div.</b><i>name</i> to indicate a <b>div</b> element with class <i>name</i>. Only one element in an HTML document can have a given <b>id</b>, whereas many elements (even of different tag types) can share the same <b>class</b>. All three aspects of an element---its tag type, its <b>id</b> (if it has one), and its <b>class</b> attributes (if it has any)---can be used to identify an element as a candidate for visual formatting. <hr> @@ -17,7 +17,7 @@ CSS uses _**selector notations**_ such as **div#***name* to indicate a **div** e <iframe width="560" height="315" src="//www.youtube.com/embed/yX1tMdBuG3g" frameborder="0" allowfullscreen></iframe> -In a Haml template, lines beginning with **%** expand into the corresponding HTML opening tag, with no closing tag needed since Haml uses indentation to determine structure. Ruby-like hashes following a tag become HTML attributes. Lines \mbox{**--beginning with a dash**} are executed as Ruby code with the result discarded, and lines \mbox{**=beginning with an equals sign**} are executed as Ruby code with the result interpolated into the HTML output. +In a Haml template, lines beginning with <b>%</b> expand into the corresponding HTML opening tag, with no closing tag needed since Haml uses indentation to determine structure. Ruby-like hashes following a tag become HTML attributes. Lines \mbox{<b>--beginning with a dash</b>} are executed as Ruby code with the result discarded, and lines \mbox{<b>=beginning with an equals sign</b>} are executed as Ruby code with the result interpolated into the HTML output. <hr> @@ -29,6 +29,6 @@ In a Haml template, lines beginning with **%** expand into the corresponding HTM <iframe width="560" height="315" src="//www.youtube.com/embed/X5ArSbUea_o" frameborder="0" allowfullscreen></iframe> -CSS uses _**selector notations**_ such as **div#***name* to indicate a **div** element whose **id** is *name* and **div.***name* to indicate a **div** element with class *name*. Only one element in an HTML document can have a given **id**, whereas many elements (even of different tag types) can share the same **class**. All three aspects of an element---its tag type, its **id** (if it has one), and its **class** attributes (if it has any)---can be used to identify an element as a candidate for visual formatting. +CSS uses ___selector notations___ such as <b>div#</b><i>name</i> to indicate a <b>div</b> element whose <b>id</b> is <i>name</i> and <b>div.</b><i>name</i> to indicate a <b>div</b> element with class <i>name</i>. Only one element in an HTML document can have a given <b>id</b>, whereas many elements (even of different tag types) can share the same <b>class</b>. All three aspects of an element---its tag type, its <b>id</b> (if it has one), and its <b>class</b> attributes (if it has any)---can be used to identify an element as a candidate for visual formatting. -<hr> \ No newline at end of file +<hr> diff --git a/tests/unit/cases/sf_bracket.md b/tests/unit/cases/sf_bracket.md index 49f87800..c7b39653 100644 --- a/tests/unit/cases/sf_bracket.md +++ b/tests/unit/cases/sf_bracket.md @@ -1 +1 @@ -If you don't want to use Git at all, you can download the code in a ZIP archive using the **Download ZIP** button on the GitHub page \ No newline at end of file +If you don't want to use Git at all, you can download the code in a ZIP archive using the <b>Download ZIP</b> button on the GitHub page \ No newline at end of file diff --git a/tests/unit/cases/sidebar.md b/tests/unit/cases/sidebar.md index 21e4598a..6e3d558e 100644 --- a/tests/unit/cases/sidebar.md +++ b/tests/unit/cases/sidebar.md @@ -15,6 +15,7 @@ |||info -The use of angle brackets for tags comes from _**SGML**_ (Standard Generalized Markup Language), a codified standardization of IBM's Generalized Markup Language, developed in the 1960s for encoding computer-readable project documents. +The use of angle brackets for tags comes from ___SGML___ (Standard Generalized Markup Language), a codified standardization of IBM's Generalized Markup Language, developed in the 1960s for encoding computer-readable project documents. + +||| -||| \ No newline at end of file diff --git a/tests/unit/cases/sidebargraphic.md b/tests/unit/cases/sidebargraphic.md index bbf0101c..63256a74 100644 --- a/tests/unit/cases/sidebargraphic.md +++ b/tests/unit/cases/sidebargraphic.md @@ -1,17 +1,12 @@ |||xdiscipline -**Alan Perlis** (1922--1990) was the first recipient of the Turing Award (1966), conferred for his influence on advanced programming languages and compilers. In 1958 he helped design ALGOL, which has influenced virtually every imperative programming language including C and Java. To avoid FORTRAN's syntactic and semantic problems, ALGOL was the first language described in terms of a formal grammar, the eponymous _**Backus-Naur form**_ (named for Turing award winner Jim Backus and his colleague Peter Naur). -![Alan Perlis](ch_javascript/figs/perlis.png) +**Alan Perlis** (1922--1990) was the first recipient of the Turing Award (1966), conferred for his influence on advanced programming languages and compilers. In 1958 he helped design ALGOL, which has influenced virtually every imperative programming language including C and Java. To avoid FORTRAN's syntactic and semantic problems, ALGOL was the first language described in terms of a formal grammar, the eponymous ___Backus-Naur form___ (named for Turing award winner Jim Backus and his colleague Peter Naur). +<img alt='Alan Perlis' src='ch_javascript/figs/perlis.png' style='width:200px' /> ||| - -|||xdiscipline - -![](ch_foreword/figs/patterson.png) -||| - +<img alt='' src='ch_foreword/figs/patterson.png' style='width:200px' /> -**David Patterson** recently retired from a 40-year career as a Professor of Computer Science at UC Berkeley. In the \ No newline at end of file +<b>David Patterson</b> recently retired from a 40-year career as a Professor of Computer Science at UC Berkeley. In the diff --git a/tests/unit/cases/summary.md b/tests/unit/cases/summary.md index e7ee8795..37a4ad36 100644 --- a/tests/unit/cases/summary.md +++ b/tests/unit/cases/summary.md @@ -1,4 +1,4 @@ --- -**Summary:** Following the Agile Manifesto's emphasis on customer cooperation over contracts, an Agile team's notion of “cost estimation” is therefore more about advising the client on what team size can provide the maximum efficiency, following Brooks's Law that there is a point of diminishing returns on team size (see Section sec:bdd_idea_FnP). The Agile team's goal in the scoping process is to identify that point, then ramp the team up to that size over time. Agile companies bid costs for time and materials based on short discussions with external customers. As we shall see in Section sec:BDD_P_and_D, this approach is in sharp contrast with companies that follow plan-and-document processes, which promise customers a set of features for an agreed upon cost by an agreed upon date. +<b>Summary:</b> Following the Agile Manifesto's emphasis on customer cooperation over contracts, an Agile team's notion of “cost estimation” is therefore more about advising the client on what team size can provide the maximum efficiency, following Brooks's Law that there is a point of diminishing returns on team size (see Section sec:bdd_idea_FnP). The Agile team's goal in the scoping process is to identify that point, then ramp the team up to that size over time. Agile companies bid costs for time and materials based on short discussions with external customers. As we shall see in Section sec:BDD_P_and_D, this approach is in sharp contrast with companies that follow plan-and-document processes, which promise customers a set of features for an agreed upon cost by an agreed upon date. --- \ No newline at end of file diff --git a/tests/unit/cases/tablefigure.md b/tests/unit/cases/tablefigure.md index bfcbf9be..6943a776 100644 --- a/tests/unit/cases/tablefigure.md +++ b/tests/unit/cases/tablefigure.md @@ -1,15 +1,15 @@ -We can therefore say that the expression **3+2** results in calling **Fixnum#+** on the receiver **3**. +We can therefore say that the expression <b>3+2</b> results in calling <b>Fixnum#+</b> on the receiver <b>3</b>. file content content -**Figure 1.1: The first column is Ruby's syntactic sugar for common operations, the second column shows the explicit method call, and the third column shows how to perform the same method call using Ruby's **send**, which accepts either a string or (more idiomatically) a symbol for the method name.** +**<p style="font-size: 10px">Figure 1.1: The first column is Ruby's syntactic sugar for common operations, the second column shows the explicit method call, and the third column shows how to perform the same method call using Ruby's <b>send</b>, which accepts either a string or (more idiomatically) a symbol for the method name.</p>** file content content -**Figure 1.2: Comparing Amazon.com and Healthcare.gov during its first three months. (Thorp13) After its stumbling start, the deadline was extended from December 15, 2013 to March 31, 2014, which explains the lower goal in customers per day in December. Note that availability for ACA does *not* include time for “scheduled maintenance,” which Amazon does include (Zients13). The error rate was for significant errors on the forms sent to insurance companies (Horsley13). The site was widely labeled by security experts as insecure, as the developers were under tremendous pressure to get proper functionality, and little attention was paid to security (Harrington13)** +**<p style="font-size: 10px">Figure 1.2: Comparing Amazon.com and Healthcare.gov during its first three months. (Thorp13) After its stumbling start, the deadline was extended from December 15, 2013 to March 31, 2014, which explains the lower goal in customers per day in December. Note that availability for ACA does <i>not</i> include time for “scheduled maintenance,” which Amazon does include (Zients13). The error rate was for significant errors on the forms sent to insurance companies (Horsley13). The site was widely labeled by security experts as insecure, as the developers were under tremendous pressure to get proper functionality, and little attention was paid to security (Harrington13)</p>** file content content -**Figure 1.3: Some common Ruby methods on collections. For those that expect a block, the “Block” column shows the number of arguments expected by the block; if blank, the method doesn't expect a block. For example, a call to **sort**, whose block expects 2 arguments, might look like: **c.sort { |a,b| a $<=>$ b }**. These methods all return a new object rather than modifying the receiver, but some methods also have a *destructive* variant ending in **!**, for example **sort!**, that modify their argument in place (and also return the new value). Use destructive methods with extreme care, if at all.** \ No newline at end of file +**<p style="font-size: 10px">Figure 1.3: Some common Ruby methods on collections. For those that expect a block, the “Block” column shows the number of arguments expected by the block; if blank, the method doesn't expect a block. For example, a call to <b>sort</b>, whose block expects 2 arguments, might look like: <b>c.sort { |a,b| a $<=>$ b }</b>. These methods all return a new object rather than modifying the receiver, but some methods also have a <i>destructive</i> variant ending in <b>!</b>, for example <b>sort!</b>, that modify their argument in place (and also return the new value). Use destructive methods with extreme care, if at all.</p>** diff --git a/tests/unit/cases/tabular.md b/tests/unit/cases/tabular.md index b23bd696..ee3059bf 100644 --- a/tests/unit/cases/tabular.md +++ b/tests/unit/cases/tabular.md @@ -3,23 +3,23 @@ |$2^4$|$2^3$|$2^2$|$2^1$|$2^0$| -|**Selector**|**What is selected**| +|<b>Selector</b>|<b>What is selected</b>| |-|-| -|**h1**|Any **h1** element| -|**div#message**|The **div** whose ID is **message**| -|**.red**|Any element with class **red**| -|**div.red, h1**|The **div** with class **red**, or any **h1**| -|**div#message h1**|An **h1** element that's a child of (inside<br/>of) **div#message**| -|**a.lnk**|**a** element with class **lnk**| -|**a.lnk:hover**|**a** element with class **lnk**, when hovered over| +|<b>h1</b>|Any <b>h1</b> element| +|<b>div#message</b>|The <b>div</b> whose ID is <b>message</b>| +|<b>.red</b>|Any element with class <b>red</b>| +|<b>div.red, h1</b>|The <b>div</b> with class <b>red</b>, or any <b>h1</b>| +|<b>div#message h1</b>|An <b>h1</b> element that's a child of (inside of) <b>div#message</b>| +|<b>a.lnk</b>|<b>a</b> element with class <b>lnk</b>| +|<b>a.lnk:hover</b>|<b>a</b> element with class <b>lnk</b>, when hovered over| -|**Attribute**|**Example values**|**Attribute**|**Example values**| +|<b>Attribute</b>|<b>Example values</b>|<b>Attribute</b>|<b>Example values</b>| |-|-|-|-| -|font-family|**"Times, serif"**|background-color|**red**, **#c2eed6** (RGB values)| -|font-weight|**bold**|border|**1px solid blue**| -|font-size|**14pt**, **125%**, **12px**|text-align|**right**| -|font-style|**italic**|text-decoration|**underline**| -|color|**black**|vertical-align|**middle**| -|margin|**4px**|padding|**1cm**| \ No newline at end of file +|font-family|<b>"Times, serif"</b>|background-color|<b>red</b>, <b>#c2eed6</b> (RGB values)| +|font-weight|<b>bold</b>|border|<b>1px solid blue</b>| +|font-size|<b>14pt</b>, <b>125%</b>, <b>12px</b>|text-align|<b>right</b>| +|font-style|<b>italic</b>|text-decoration|<b>underline</b>| +|color|<b>black</b>|vertical-align|<b>middle</b>| +|margin|<b>4px</b>|padding|<b>1cm</b>| \ No newline at end of file diff --git a/tests/unit/cases/tabularx.md b/tests/unit/cases/tabularx.md index 0aa45f3c..1a397f11 100644 --- a/tests/unit/cases/tabularx.md +++ b/tests/unit/cases/tabularx.md @@ -1,14 +1,14 @@ -|**Item**|**3 points**|**2 points**|**1 point**| +|<b>Item</b>|<b>3 points</b>|<b>2 points</b>|<b>1 point</b>| |-|-|-|-| -|**Preparation**|Presentation was rehearsed thoroughly, few hiccups|Presentation is mostly fluid, with the exception of maybe 1 or 2<br/>underprepared people|Sloppy presentation, no clear direction or disorganized| -|**Customer Need**||Included brief statement of customer problem and scope of project|Not clear what the customer really wanted or what problem the group is<br/>trying to solve| -|**Technical Discussion**|Clearly conveyed some technical details of the app. Discussed a<br/>technical challenge in an understandable way|Attempted to talk about technical aspects of app, mostly successfully.<br/>Either no technical challenge or not able to clearly convey technical<br/>topics|Little or no technical coverage of their app. Implementation details<br/>are unclear.| -|**Customer Interaction**|Clearly describe relationship with customer. Talk about goods, bads, and<br/>what was learned|Talked about customer interaction to some extent|Mentioned customer interaction, but no details about the experience| -|**Development Practices**|Explained clearly their implementation of agile in their iterations.<br/>Reflect on what went well and what did not. Had a method to taking on<br/>iterations.|Briefly explain development practices. Give an idea of approaches they<br/>took to iterations/Agile, but don't reflect on their effectiveness|Doesn't appear they took methodology seriously, and/or no evidence of<br/>concrete implementation of agile principles| +|<b>Preparation</b>|Presentation was rehearsed thoroughly, few hiccups|Presentation is mostly fluid, with the exception of maybe 1 or 2 underprepared people|Sloppy presentation, no clear direction or disorganized| +|<b>Customer Need</b>||Included brief statement of customer problem and scope of project|Not clear what the customer really wanted or what problem the group is trying to solve| +|<b>Technical Discussion</b>|Clearly conveyed some technical details of the app. Discussed a technical challenge in an understandable way|Attempted to talk about technical aspects of app, mostly successfully. Either no technical challenge or not able to clearly convey technical topics|Little or no technical coverage of their app. Implementation details are unclear.| +|<b>Customer Interaction</b>|Clearly describe relationship with customer. Talk about goods, bads, and what was learned|Talked about customer interaction to some extent|Mentioned customer interaction, but no details about the experience| +|<b>Development Practices</b>|Explained clearly their implementation of agile in their iterations. Reflect on what went well and what did not. Had a method to taking on iterations.|Briefly explain development practices. Give an idea of approaches they took to iterations/Agile, but don't reflect on their effectiveness|Doesn't appear they took methodology seriously, and/or no evidence of concrete implementation of agile principles| -|**Approach**|**Technologies \& Tools**|**Main Pros**|**Main Cons**|| +|<b>Approach</b>|<b>Technologies \& Tools</b>|<b>Main Pros</b>|<b>Main Cons</b>|| |-|-|-|-|-| -|Responsive or mobile-only Web site|HTML5, CSS, JavaScript; any development environment|Portable across devices, so no need to develop/maintain multiple<br/> versions; updates automatically|May not show up in searches; not listed in app stores; may lack<br/> access to certain platform hardware features, such as<br/> biometrics-based identification|| -|Native app|Java (Android) or Objective-C (iOS) with mobile-specific IDE|Best performance; guaranteed access to all platform hardware features|Must rely on users to download and install updates; supporting<br/> multiple platforms requires maintaining multiple codebases|| -|Wrapped app|HTML5, CSS, JavaScript; framework such as PhoneGap required to<br/> “package” app for distribution|Portable, develop with any IDE, but still get installable app that<br/> appears in app stores and searches|Must rely on users to download and install updates|| \ No newline at end of file +|Responsive or mobile-only Web site|HTML5, CSS, JavaScript; any development environment|Portable across devices, so no need to develop/maintain multiple versions; updates automatically|May not show up in searches; not listed in app stores; may lack access to certain platform hardware features, such as biometrics-based identification|| +|Native app|Java (Android) or Objective-C (iOS) with mobile-specific IDE|Best performance; guaranteed access to all platform hardware features|Must rely on users to download and install updates; supporting multiple platforms requires maintaining multiple codebases|| +|Wrapped app|HTML5, CSS, JavaScript; framework such as PhoneGap required to “package” app for distribution|Portable, develop with any IDE, but still get installable app that appears in app stores and searches|Must rely on users to download and install updates|| \ No newline at end of file diff --git a/tests/unit/cases/textbf.md b/tests/unit/cases/textbf.md index 5302c863..f27237db 100644 --- a/tests/unit/cases/textbf.md +++ b/tests/unit/cases/textbf.md @@ -1 +1 @@ -**Recursion-1 noX** \ No newline at end of file +<b>Recursion-1 noX</b> \ No newline at end of file diff --git a/tests/unit/cases/turingwinner.md b/tests/unit/cases/turingwinner.md new file mode 100644 index 00000000..4717439b --- /dev/null +++ b/tests/unit/cases/turingwinner.md @@ -0,0 +1,24 @@ +Hello content + +|||xdiscipline +**Alan Perlis (1922--1990)** was the first recipient of the Turing Award (1966), conferred for his influence on advanced programming languages and compilers. In 1958 he helped design ALGOL, which has influenced virtually every imperative programming language including C and Java. To avoid FORTRAN's syntactic and semantic problems, ALGOL was the first language described in terms of a formal grammar, the eponymous ___Backus-Naur form___ (named for Turing award winner Jim Backus and his colleague Peter Naur). +<img alt='Alan Perlis (1922--1990)' src='turing/figs/perlis.png' style='width:200px' /> +||| + + +> In programming, everything we do is a special case of something more general---and often we know it too quickly. +> +> __Alan Perlis__ + + End content + + + +|||xdiscipline +**Vinton E. “Vint” Cerf (1943--) and Bob Kahn (1938--)** shared the 2004 Turing Award for their pioneering work on networking architecture and protocols, including TCP/IP. +<img alt='Vinton E. “Vint” Cerf (1943--) and Bob Kahn (1938--)' src='turing/figs/cerf_kahn.png' style='width:200px' /> +||| + + + +End content2 diff --git a/tests/unit/cases/turingwinner.tex b/tests/unit/cases/turingwinner.tex new file mode 100644 index 00000000..3c216473 --- /dev/null +++ b/tests/unit/cases/turingwinner.tex @@ -0,0 +1,25 @@ +Hello content +\turingwinner{perlis}{Alan Perlis (1922--1990)}% + {was the first recipient of the Turing Award (1966), conferred + for his influence on advanced programming languages and compilers. + In 1958 he helped design ALGOL, + which has influenced virtually every imperative programming language + including C and Java. To avoid FORTRAN's + syntactic and semantic problems, ALGOL was the first language + described in terms of a formal grammar, the eponymous + \w[Backus-Naur_Form]{Backus-Naur form} (named for Turing award + winner Jim Backus and his colleague Peter Naur).}% + {In programming, everything we do is a special case of + something more general---and often we know it too quickly.}{Alan + Perlis} +End content + +\turingwinner{cerf_kahn}{Vinton E. ``Vint'' Cerf (1943--) and Bob Kahn +(1938--)}% +{shared the 2004 Turing Award for their pioneering work on networking +architecture and protocols, including TCP/IP.}{}{} + +End content2 + + + diff --git a/tests/unit/cases/verbatim.md b/tests/unit/cases/verbatim.md index bd031650..25b6b3c5 100644 --- a/tests/unit/cases/verbatim.md +++ b/tests/unit/cases/verbatim.md @@ -12,9 +12,9 @@ public final class Integer extends Number implements Comparable<Integer> { public int compareTo(Integer anotherInteger) { int thisVal = this.value; int anotherVal = anotherInteger.value; - return (thisVal<anotherVal ? -1 : (thisVal==anotherVal ? 0 : 1)); + return (thisVal<anotherVal ? -1 : (thisVal\=\=anotherVal ? 0 : 1)); } // other methods omitted } -``` \ No newline at end of file +``` diff --git a/tests/unit/cases/w.md b/tests/unit/cases/w.md index 7a3fe05b..e4c08217 100644 --- a/tests/unit/cases/w.md +++ b/tests/unit/cases/w.md @@ -1,5 +1,5 @@ -Many SaaS apps, including those based on Rails, follow the _**Model-View-Controller**_ (MVC) architectural pattern. In MVC, Models deal with the app's _**resources**_ such as users or blog posts, Views present information to the user via the browser, and Controllers map the user's browser actions to application code. +Many SaaS apps, including those based on Rails, follow the ___Model-View-Controller___ (MVC) architectural pattern. In MVC, Models deal with the app's ___resources___ such as users or blog posts, Views present information to the user via the browser, and Controllers map the user's browser actions to application code. -* Using _**AJAX**_, or Asynchronous JavaScript And XML, JavaScript code can make HTTP requests to a Web server *without* triggering a page reload. The information in the response can then be used to modify page elements in place, giving a richer and often more responsive user experience than traditional Web pages. Rails partials and controller actions can be readily used to handle AJAX interactions. \ No newline at end of file +* Using ___AJAX___, or Asynchronous JavaScript And XML, JavaScript code can make HTTP requests to a Web server <i>without</i> triggering a page reload. The information in the response can then be used to modify page elements in place, giving a richer and often more responsive user experience than traditional Web pages. Rails partials and controller actions can be readily used to handle AJAX interactions. diff --git a/tests/unit/cases/x.md b/tests/unit/cases/x.md index 21f44b97..a5aef04d 100644 --- a/tests/unit/cases/x.md +++ b/tests/unit/cases/x.md @@ -1 +1 @@ -The _**SMART**_ acronym captures the desirable features of a good user story: Specific, Measurable, Achievable, Relevant, and Timeboxed. \ No newline at end of file +The ___SMART___ acronym captures the desirable features of a good user story: Specific, Measurable, Achievable, Relevant, and Timeboxed. diff --git a/tests/unit/test_render_markdown.py b/tests/unit/test_render_markdown.py index 0933175c..a9d9b56f 100644 --- a/tests/unit/test_render_markdown.py +++ b/tests/unit/test_render_markdown.py @@ -22,7 +22,7 @@ def load_md(path): def load_file(path): dn = os.path.dirname(os.path.realpath(__file__)) fn = os.path.join(dn, path) - with open(fn, 'r') as file: + with open(fn, 'r', encoding='utf-8') as file: return file.read() @@ -34,6 +34,9 @@ def make_converter(path, refs, chapter_num, load_workspace_file): class TestSuite(unittest.TestCase): + + maxDiff = None + def write_md(self, name, refs={}, chapter_num=1, load_workspace_file=lambda _: _): path = "cases/{}".format(name) converter = make_converter(path, refs, chapter_num, load_workspace_file) @@ -297,3 +300,9 @@ def test_tabularx(self): def test_twoicons(self): self.run_case("twoicons") + + def test_turingwinner(self): + self.run_case("turingwinner") + + def test_equation(self): + self.run_case("equation") diff --git a/tests/unit/test_toc.py b/tests/unit/test_toc.py index a1b424b6..35fc6c53 100644 --- a/tests/unit/test_toc.py +++ b/tests/unit/test_toc.py @@ -102,4 +102,5 @@ def test_toc_generation_exception(self): ('\\section{{\\tt Comparable} and {\\tt Comparator}}', 'Comparable and Comparator') ]) def test_name_generation(self, case, should_be): - self.assertEqual(get_name(case), should_be) + name, _ = get_name(case) + self.assertEqual(name, should_be) diff --git a/wercker.yml b/wercker.yml index f8a40aeb..837cc6aa 100644 --- a/wercker.yml +++ b/wercker.yml @@ -1,4 +1,7 @@ -box: python:3.7 +box: + id: python:3.7 + username: $DOCKER_USER + password: $DOCKER_PASSWORD no-response-timeout: 10 build: steps: