package rabbitescape.ui.swing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import rabbitescape.engine.util.Util;
/**
* @brief encapsulates what has been retrieved about a github issue
*/
public class GitHubIssue
{
private int number; /** < @brief github issue number. */
private boolean isLevel;
private boolean isBug;
private String body = ""; /** < @brief body text excluding world text. */
private String title;
private ArrayList<String> wrappedWorlds; /** < @brief Worlds are []. These have \n */
private int worldIndex = 0; /** < @brief Some issues have multiple worlds: current selected index */
private static final String replaceWorldsWith = "\n-----\n";
private static final String commentSeparator = "\n*****\n";
/**
* @brief Creates a GitHubIssue with no labels.
*/
public GitHubIssue( int gitHubIssueNumber,
String gitHubIssueTitle,
String openingCommentBody )
{
this( gitHubIssueNumber,
gitHubIssueTitle,
openingCommentBody,
new String[] {} );
}
public GitHubIssue( int gitHubIssueNumber,
String gitHubIssueTitle,
String openingCommentBody,
String[] labels )
{
wrappedWorlds = new ArrayList<String>();
number = gitHubIssueNumber;
addToBody( openingCommentBody );
title = stripEscape( gitHubIssueTitle );
for ( int i = 0; i < labels.length; i++ )
{
if ( 0 == labels[i].compareTo( "bug" ) )
{
isBug = true;
}
else if ( 0 == labels[i].compareTo( "level" ) )
{
isLevel = true;
}
}
}
public int getNumber()
{
return number;
}
public boolean isLevel()
{
return isLevel;
}
public boolean isBug()
{
return isBug;
}
/**
* @return true if it can be set (is not out of range)
*/
public boolean setCurrentWorldIndex( int index )
{
if ( wrappedWorlds.size() > index )
{
worldIndex = index;
return true;
}
return false;
}
public int getCurrentWorldIndex()
{
return worldIndex;
}
public String getCurrentWorld()
{
if ( wrappedWorlds.size() > worldIndex )
{
return wrappedWorlds.get( worldIndex );
}
return null;
}
public void fetchComments( GitHubClient ghc )
{
ghc.fetchComments( this );
}
/**
* @param i
* Some issues contain multiple worlds. Ask for them by index.
* @return World string with newline characters.
*/
public String getWorld( int i )
{
if ( i >= wrappedWorlds.size() )
{
return null;
}
return wrappedWorlds.get( i );
}
public String getBody()
{
return body;
}
/**
* @brief Keeps a copy of the body text and parses out the worlds.
*/
public void addToBody( String bodyIn )
{
// Positions of worlds in the body text;
ArrayList<Integer> startIndices = new ArrayList<Integer>();
ArrayList<Integer> endIndices = new ArrayList<Integer>();
String bodyAdd = "" + bodyIn;
ArrayList<String> wrappedWorldsAdd = new ArrayList<String>();
findBacktickWorlds( bodyAdd, wrappedWorldsAdd, startIndices, endIndices );
findIndentWorlds( bodyAdd, wrappedWorldsAdd, startIndices, endIndices );
ArrayList<String> newWorldOrder = new ArrayList<String>(
wrappedWorldsAdd.size() );
for ( int i = 0; i < startIndices.size(); i++ )
{
newWorldOrder.add( "" );
}
// re-order worlds and remove worlds from body text
for ( int i = 0; i < startIndices.size(); i++ )
{
int max = Collections.max( startIndices );
int maxIndex = -1;
for ( int j = 0; j < startIndices.size(); j++ )
{
if ( max == startIndices.get( j ) )
{
maxIndex = j;
break;
}
}
newWorldOrder.set( startIndices.size() - 1 - i,
wrappedWorldsAdd.get( maxIndex ) );
bodyAdd = bodyAdd.substring( 0, startIndices.get( maxIndex ) ) +
replaceWorldsWith +
bodyAdd.substring( endIndices.get( maxIndex ) );
startIndices.set( maxIndex, 0 ); // So it is not found again
}
wrappedWorlds.addAll( newWorldOrder );
bodyAdd = stripEscape( bodyAdd );
bodyAdd = realNewlines( bodyAdd );
body = body + commentSeparator + bodyAdd;
}
/**
* @brief Add location of world in the body string to the list. Only add if
* the new world is not inside another. Backtick worlds are parsed
* first, so they win.
*/
private static void checkAddWorldIndices( ArrayList<Integer> startIndices,
ArrayList<Integer> endIndices,
int startIndex,
int endIndex )
{
for ( int i = 0; i < startIndices.size(); i++ )
{
if ( startIndex >= startIndices.get( i )
&& startIndex < endIndices.get( i ) )
{ // It's in the range of another world, don't add.
return;
}
}
startIndices.add( startIndex );
endIndices.add( endIndex );
}
/**
* @brief parse out worlds from markdown contained in triple backticks
*/
private static void findBacktickWorlds( String bodyAdd,
ArrayList<String> braveNewWrappedWorlds,
ArrayList<Integer> startIndices,
ArrayList<Integer> endIndices )
{
Pattern worldPattern = Pattern.compile( "(\\\\n)?```(.*?)```" );
Matcher worldMatcher = worldPattern.matcher( bodyAdd );
while ( worldMatcher.find() )
{
String worldWrapped = worldMatcher.group( 2 );
braveNewWrappedWorlds.add( fixWorld( worldWrapped ) );
checkAddWorldIndices( startIndices,
endIndices,
worldMatcher.start(),
worldMatcher.end() );
}
}
/**
* @brief parse out worlds from markdown contained in indent blocks Appends
* to the ArrayList<Integer> describing the worlds location.
*/
private static void findIndentWorlds( String bodyAdd,
ArrayList<String> braveNewWrappedWorlds,
ArrayList<Integer> startIndices,
ArrayList<Integer> endIndices )
{
// at least 4 spaces or a tab
// the json has \n in, not newline char
// after java compiler \\\\ becomes \\, after regex compile \
Pattern firstLinePattern = Pattern.compile( "\\\\n(\\\\t| {4,}+)" );
Matcher firstLineMatcher = firstLinePattern.matcher( bodyAdd );
int worldStart, worldEnd;
while ( firstLineMatcher.find() )
{
worldStart = firstLineMatcher.start();
String blockPrefix = firstLineMatcher.group( 1 );
if ( 0 == blockPrefix.compareTo( "\\t" ) )
{
blockPrefix = "\\\\t"; // Reform so regex is char pair, not tab
// char.
}
Pattern subsequentLinePattern = Pattern.compile( "\\\\n"
+ blockPrefix + "(.+?)\\\\n" );
Matcher subsequentLineMatcher = subsequentLinePattern
.matcher( bodyAdd );
// Do I need to store the result of region back in the Matcher?
subsequentLineMatcher.region( firstLineMatcher.start(),
bodyAdd.length() - 1 );
String worldWrapped = "";
int prevEndIndex = -1;
while ( subsequentLineMatcher.find() )
{
/*
* Check for lines between matches, note this is to the start of
* the whole match including the indent string. First time
* through, let the -1 past.
*/
if ( -1 != prevEndIndex &&
subsequentLineMatcher.start() != prevEndIndex )
{
break;
}
worldWrapped = worldWrapped + subsequentLineMatcher.group( 1 )
+ "\n";
prevEndIndex = subsequentLineMatcher.end() - 2;
// the end of the group is 2 chars before the end of the whole
// match, so it
// can find the \n again to start the next line.
subsequentLineMatcher.region( subsequentLineMatcher.end( 1 ),
bodyAdd.length() );
}
braveNewWrappedWorlds.add( fixWorld( worldWrapped ) );
if ( -1 == prevEndIndex )
{ // indent block runs to the end of the body
worldEnd = bodyAdd.length();
checkAddWorldIndices( startIndices, endIndices, worldStart,
worldEnd );
break;
}
else
{
worldEnd = prevEndIndex;
firstLineMatcher = firstLineMatcher.region( prevEndIndex,
bodyAdd.length() );
checkAddWorldIndices( startIndices, endIndices, worldStart,
worldEnd );
}
}
}
/**
* @brief Perform some automatic fixing Can't be as strict as when loading
* from files.
*/
private static String fixWorld( String world )
{
String fixed = "" + world;
fixed = stripEscape( fixed );
fixed = realNewlines( fixed );
// Remove blank lines
fixed = fixed.replaceAll( "\n\n", "\n" );
fixed = fixed.replaceAll( "^\n", "" );
// Strip trailing spaces from meta lines
fixed = Util.regexRemovePreserveGroup( fixed, "^(:.*?) *?$", Pattern.MULTILINE );
return fixed;
}
public String getTitle()
{
return title;
}
public void setTitle( String titleIn )
{
this.title = stripEscape( titleIn );
}
/**
* @brief Tidy up escape characters. - Remove \ before " - Remove \r to
* covert Win EOL to Unix EOL - Undouble \\
*/
public static String stripEscape( String s1 )
{
String s2;
s2 = s1.replaceAll( "(\\\\r)", "" );
s2 = Util.regexRemovePreserveGroup( s2, "\\\\(\\\")" );
// Undouble slashes
// I don't understand why this does not work without the capturing group
// and back reference.
s2 = Util.regexRemovePreserveGroup( s2, "(\\\\)\\\\" );
return s2;
}
/**
* @brief Replace \n with actual newline chars.
*/
public static String realNewlines( String s )
{
return s.replaceAll( "\\\\n", "\n" );
}
}