Java tutorial
/* * MIT License * * Copyright (c) 2016 David Russell * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.gitpitch.models; import com.gitpitch.models.SlideshowModel; import com.gitpitch.utils.*; import com.gitpitch.services.ImageService; import com.gitpitch.services.VideoService; import com.gitpitch.services.GISTService; import org.apache.commons.io.FilenameUtils; import com.google.inject.assistedinject.Assisted; import javax.annotation.Nullable; import play.Logger; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.inject.*; /* * Rendering model for PITCHME.md markdown. */ public class MarkdownModel implements Markdown { private final Logger.ALogger log = Logger.of(this.getClass()); private final ImageService imageService; private final VideoService videoService; private final GISTService gistService; private final MarkdownRenderer mrndr; private final String markdown; private String hSlideDelim = HSLIDE_DELIM_DEFAULT; private String vSlideDelim = VSLIDE_DELIM_DEFAULT; @Inject public MarkdownModel(ImageService imageService, VideoService videoService, GISTService gistService, @Nullable @Assisted MarkdownRenderer mrndr) { this.imageService = imageService; this.videoService = videoService; this.gistService = gistService; this.mrndr = mrndr; String consumed = null; if (mrndr != null) { String gitRawBase = mrndr.gitRawBase(); Path mdPath = mrndr.filePath(PITCHME_MD); File mdFile = mdPath.toFile(); if (mdFile.exists()) { Optional<SlideshowModel> ssmo = mrndr.ssm(); final PitchParams pp = ssmo.isPresent() ? ssmo.get().params() : null; final YAMLOptions yOpts = ssmo.isPresent() ? ssmo.get().options() : null; if (yOpts != null) { hSlideDelim = yOpts.hasHorzDelim(pp) ? yOpts.fetchHorzDelim(pp) : HSLIDE_DELIM_DEFAULT; vSlideDelim = yOpts.hasVertDelim(pp) ? yOpts.fetchVertDelim(pp) : VSLIDE_DELIM_DEFAULT; } try (Stream<String> stream = Files.lines(mdPath)) { consumed = stream.map(md -> { return process(md, pp, yOpts, gitRawBase); }).collect(Collectors.joining("\n")); consumed = postProcess(consumed, pp, yOpts, gitRawBase); } catch (Exception mex) { log.warn("Markdown processing ex={}", mex); consumed = "PITCHME.md could not be parsed."; } } else { log.warn("Markdown file does not exist, {}", mdFile); } } this.markdown = consumed; } /* * Process text, image, video and gist content * in PITCHME.md for online viewing. */ private String process(String md, PitchParams pp, YAMLOptions yOpts, String gitRawBase) { md = processBackwardCompatDelim(md); if (slideDelimFound(md)) { /* * Clean only delimiter fragments of whitespace * noise before processing. Preserve end-of-line * characters on all non-delimiter fragments. */ md = md.trim(); if (videoDelimFound(md)) { /* * Inject slide specific video background: * * <!-- .slide: data-background-video="vidUrl" --> */ String videoBgUrl = videoService.extractBgUrl(md, gitRawBase, this); return new StringBuffer(delimiter(md)).append(videoService.buildBackground(videoBgUrl)).toString(); } else if (imageDelimFound(md)) { /* * Inject slide specific image background: * * <!-- .slide: data-background-image="imgUrl" --> */ String imageBgUrl = imageService.extractBgUrl(md, gitRawBase, this); String bgSize = (yOpts != null) ? yOpts.fetchImageBgSize(pp) : YAMLOptions.DEFAULT_BG_SIZE; return new StringBuffer(delimiter(md)).append(imageService.buildBackground(pp, imageBgUrl, bgSize)) .toString(); } else if (gistDelimFound(md)) { String gist = new StringBuffer(gistService.build(md, pp, yOpts, this)).toString(); return gist; } if (yOpts != null && yOpts.hasImageBg()) { /* * Inject slideshow-wide background: * * <!-- .slide: data-background-image="imgUrl" --> */ return new StringBuffer(md).append(imageService.buildBackground(pp, yOpts)).toString(); } else { /* * No customization on slide delimiter or background, * return unmodified. */ return md; } } else if (markdownLinkFound(md)) { /* * Link found in markdown fragment, process. */ int delim = md.indexOf(MD_LINK_DELIM); if (delim == NOT_FOUND) { /* * Valid link syntax not found, return unmodified. */ return md; } else { /* * Valid link syntax found, now process link to * handle absolute, (GitHub) relative and video links. */ String splitLeft = md.substring(0, delim + 2); String splitRight = md.substring(delim + 2); /* * Clone and clean splitRight string to begin * extraction of the pitchLink. */ String pitchLink = cloneLink(splitRight); pitchLink = extractLink(pitchLink, gitRawBase); if (videoService.isVideo(pitchLink)) { return videoService.buildVideo(pitchLink); } else if (linkAbsolute(splitRight)) { return md; } else { return splitLeft + gitRawBase + splitRight; } } } if (imageService.tagFound(md)) { String tagLink = imageService.tagLink(md); if (linkAbsolute(tagLink)) { return md; } else { String absTagLink = gitRawBase + tagLink; return md.replace(tagLink, absTagLink); } } else { /* * Link-free markdown, return unmodified. */ return md; } } public String produce() { return markdown; } /* * Process text, image, video and gist content * in PITCHME.md for offline viewing. */ public String offline(String md) { if (videoService.found(md)) { return videoService.offline(); } else if (gistMarkdown(md)) { return gistService.offline(); } else if (imageService.background(md)) { return imageService.buildBackgroundOffline(md); } else if (imageService.inline(md)) { /* * Link found in markdown fragment, process. */ int delim = md.indexOf(MD_LINK_DELIM); if (delim == NOT_FOUND) { /* * Valid link syntax not found, return unmodified. */ return md; } else { /* * Valid link syntax found, now process link to * handle absolute, (GitHub) relative and video links. */ String splitLeft = md.substring(0, delim + 2); String splitRight = md.substring(delim + 2); /* * Clone and clean splitRight string to begin * extraction of the pitchLink. */ String pitchLink = cloneLink(splitRight); pitchLink = extractLink(pitchLink, null); String imageName = FilenameUtils.getName(pitchLink); return imageService.buildInlineOffline(imageName); } } else if (imageService.tagFound(md)) { return imageService.buildTagOffline(md); } else { /* * Link-free markdown, return unmodified. */ return md; } } /* * Return image asset URL from Markdown fragment. */ public String offlineAssets(String md) { if (imageService.background(md)) { String frag = md.substring(MD_IMAGE_OPEN.length()); String url = frag.substring(0, frag.indexOf("\"")); return url; } else if (imageService.inline(md)) { /* * Link found in markdown fragment, process. */ int delim = md.indexOf(MD_LINK_DELIM); if (delim == NOT_FOUND) { /* * Valid link syntax not found, return unmodified. */ return null; } else { /* * Valid link syntax found, now process link to * handle absolute, (GitHub) relative and video links. */ String splitLeft = md.substring(0, delim + 2); String splitRight = md.substring(delim + 2); /* * Clone and clean splitRight string to begin * extraction of the pitchLink. */ String pitchLink = cloneLink(splitRight); int spaceIdx = pitchLink.indexOf(MD_LINK_SPACE); int brcktIdx = pitchLink.indexOf(MD_LINK_BRCKT); String origLink = null; if (spaceIdx == NOT_FOUND || spaceIdx > brcktIdx) { origLink = pitchLink.substring(0, brcktIdx); } else { origLink = pitchLink.substring(0, spaceIdx); } return origLink; } } if (imageService.tagFound(md)) { /* * Img tag found in markdown fragment, process. */ return imageService.tagLink(md); } else { return null; } } private boolean imageDelimFound(String md) { return md.startsWith(horizImageDelim()) || md.startsWith(vertImageDelim()); } private boolean videoDelimFound(String md) { return md.startsWith(horizVideoDelim()) || md.startsWith(vertVideoDelim()); } private boolean gistDelimFound(String md) { return md.startsWith(horizGISTDelim()) || md.startsWith(vertGISTDelim()); } private String delimiter(String md) { return (md.startsWith(hSlideDelim)) ? horizDelim() : vertDelim(); } private boolean slideDelimFound(String md) { return md.startsWith(horizDelim()) || md.startsWith(vertDelim()); } private boolean markdownLinkFound(String md) { return md.startsWith(MD_LINK_OPEN); } private boolean gistMarkdown(String md) { return md.contains(DATA_GIST_ATTR); } private String cloneLink(String link) { String cloned = new String(link); /* * Strip slash prefix from relative links. */ if (cloned.startsWith(MD_LINK_SLASH)) { return cloned.substring(1); } else { return cloned; } } private String extractLink(String link, String gitRawBase) { try { int spaceIdx = link.indexOf(MD_LINK_SPACE); int brcktIdx = link.indexOf(MD_LINK_BRCKT); String origLink = null; if (spaceIdx == NOT_FOUND || spaceIdx > brcktIdx) { origLink = link.substring(0, brcktIdx); } else { origLink = link.substring(0, spaceIdx); } if (linkAbsolute(link)) { link = origLink; } else { link = gitRawBase + origLink; } return link; } catch (Exception lex) { log.warn("extractLink: link={}, ex={}", link, lex); } return null; } private String postProcess(String md, PitchParams pp, YAMLOptions yOpts, String gitRawBase) { if (yOpts != null && yOpts.hasImageBg()) { if (!delimOnFirstSlide(md)) { /* * Inject slideshow-wide background for * implicit delim on first slide in the slideshow. */ md = imageService.buildBackground(pp, yOpts) + md; } } if (delimOnFirstSlide(md)) { int nIndex = md.indexOf(MD_SPACER); if (nIndex > 0) { String slimMd = md.substring(nIndex); md = slimMd; } } return md; } private boolean delimOnFirstSlide(String md) { return (md.startsWith(horizDelim()) || md.startsWith(vertDelim())); } public boolean isHorizontal(String md) { return (md.startsWith(horizDelim())) ? true : false; } public boolean linkAbsolute(String link) { return link.startsWith(MD_LINK_ABS); } public String extractImageDelim(String md) { return isHorizontal(md) ? horizImageDelim() : vertImageDelim(); } public String extractVideoDelim(String md) { return isHorizontal(md) ? horizVideoDelim() : vertVideoDelim(); } public String extractGISTDelim(String md) { return isHorizontal(md) ? horizGISTDelim() : vertGISTDelim(); } /* * Generate a key for querying the cache for matching MarkdownModel. */ public static String genKey(PitchParams pp) { return new StringBuffer(MODEL_ID).append(SLASH).append(pp.grs).append(SLASH).append(pp.user).append(SLASH) .append(pp.repo).append(PARAM_BRANCH).append(pp.branch).append(PARAM_PITCHME).append(pp.pitchme) .toString(); } public String horizDelim() { return hSlideDelim; } public String vertDelim() { return vSlideDelim; } public String horizImageDelim() { return horizDelim() + MD_HSLIDE_IMAGE; } public String vertImageDelim() { return vertDelim() + MD_VSLIDE_IMAGE; } public String horizVideoDelim() { return horizDelim() + MD_HSLIDE_VIDEO; } public String vertVideoDelim() { return vertDelim() + MD_VSLIDE_VIDEO; } public String horizGISTDelim() { return horizDelim() + MD_HSLIDE_GIST; } public String vertGISTDelim() { return vertDelim() + MD_VSLIDE_GIST; } /* * The initial releases of GitPitch used #HSLIDE and #VSLIDE * as default delimiters. To provide backwards compatability * for older presentations, when these old delimters are * detected within PITCHME.md we auto-translate these old * delimiters to the current defaults HSLIDE|VSLIDE_DELIM_DEFAULT. */ private String processBackwardCompatDelim(String md) { try { if (md.startsWith(HSLIDE_DELIM_BACK_COMPAT)) { md = md.replaceFirst(HSLIDE_DELIM_BACK_COMPAT, HSLIDE_DELIM_DEFAULT); } else if (md.startsWith(VSLIDE_DELIM_BACK_COMPAT)) { md = md.replaceFirst(VSLIDE_DELIM_BACK_COMPAT, VSLIDE_DELIM_DEFAULT); } } catch (Exception bcex) { return md; } return md; } /* * Markdown Parsing Identifiers | Delimiters. */ public static final String HSLIDE_DELIM_DEFAULT = "---"; public static final String VSLIDE_DELIM_DEFAULT = "+++"; public static final String HSLIDE_REGEX_DEFAULT = null; public static final String VSLIDE_REGEX_DEFAULT = "\\+\\+\\+"; public static final String HSLIDE_DELIM_BACK_COMPAT = "#HSLIDE"; public static final String VSLIDE_DELIM_BACK_COMPAT = "#VSLIDE"; public static final String MD_LINK_OPEN = "!["; public static final String MD_IMAGE_OPEN = "<!-- .slide: data-background-image=\""; public static final String MD_VIDEO_OPEN = "<!-- .slide: data-background-video=\""; public static final String MD_IFRAME_OPEN = "<!-- .slide: data-background-iframe=\""; public static final String MD_IMAGE_SIZE = "\" data-background-size=\""; public static final String MD_CLOSER = "\" -->"; public static final String MD_SPACER = "\n"; public static final String DATA_IMAGE_ATTR = "data-background-image="; private static final String MD_HSLIDE_IMAGE = "?image="; private static final String MD_VSLIDE_IMAGE = "?image="; private static final String MD_HSLIDE_VIDEO = "?video="; private static final String MD_VSLIDE_VIDEO = "?video="; private static final String MD_HSLIDE_GIST = "?gist="; private static final String MD_VSLIDE_GIST = "?gist="; private static final String SLASH = "/"; private static final String PARAM_BRANCH = "?b="; private static final String PARAM_PITCHME = "&p="; private static final String MD_LINK_DELIM = "]("; private static final String MD_LINK_ABS = "http"; private static final String MD_LINK_SLASH = "/"; private static final String MD_LINK_SPACE = " "; private static final String MD_LINK_BRCKT = ")"; private static final String MD_LINK_MP4 = "mp4"; private static final String DATA_GIST_ATTR = "data-gist="; private static final int NOT_FOUND = -1; /* * Model prefix identifier for cache key generator. */ private static final String MODEL_ID = "MDM:"; private static final String PITCHME_MD = "PITCHME.md"; }