mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-22 06:05:38 +00:00
Merge pull request #308 from tomaz82/utils
Add String and FilesSystem utils
This commit is contained in:
commit
094c9ccd09
|
@ -69,6 +69,10 @@ set(CORE_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.h
|
||||
|
||||
# Utils
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/FileSystemUtil.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.h
|
||||
|
||||
# Embedded assets (needed by ResourceManager)
|
||||
${emulationstation-all_SOURCE_DIR}/data/Resources.h
|
||||
)
|
||||
|
@ -136,6 +140,10 @@ set(CORE_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureResource.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureData.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/resources/TextureDataManager.cpp
|
||||
|
||||
# Utils
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/FileSystemUtil.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/utils/StringUtil.cpp
|
||||
)
|
||||
|
||||
set(EMBEDDED_ASSET_SOURCES
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
#pragma once
|
||||
#ifndef ES_CORE_STRING_UTIL_H
|
||||
#define ES_CORE_STRING_UTIL_H
|
||||
|
||||
namespace StringUtil
|
||||
{
|
||||
inline unsigned int chars2Unicode(const std::string& _string, size_t& _cursor)
|
||||
{
|
||||
const char& c = _string[_cursor];
|
||||
unsigned int result = '?';
|
||||
|
||||
if((c & 0x80) == 0) // 0xxxxxxx, one byte character
|
||||
{
|
||||
// 0xxxxxxx
|
||||
result = ((_string[_cursor++] ) );
|
||||
}
|
||||
else if((c & 0xE0) == 0xC0) // 110xxxxx, two byte character
|
||||
{
|
||||
// 110xxxxx 10xxxxxx
|
||||
result = ((_string[_cursor++] & 0x1F) << 6) |
|
||||
((_string[_cursor++] & 0x3F) );
|
||||
}
|
||||
else if((c & 0xF0) == 0xE0) // 1110xxxx, three byte character
|
||||
{
|
||||
// 1110xxxx 10xxxxxx 10xxxxxx
|
||||
result = ((_string[_cursor++] & 0x0F) << 12) |
|
||||
((_string[_cursor++] & 0x3F) << 6) |
|
||||
((_string[_cursor++] & 0x3F) );
|
||||
}
|
||||
else if((c & 0xF8) == 0xF0) // 11110xxx, four byte character
|
||||
{
|
||||
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
result = ((_string[_cursor++] & 0x07) << 18) |
|
||||
((_string[_cursor++] & 0x3F) << 12) |
|
||||
((_string[_cursor++] & 0x3F) << 6) |
|
||||
((_string[_cursor++] & 0x3F) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// error, invalid unicode
|
||||
++_cursor;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // chars2Unicode
|
||||
|
||||
inline std::string unicode2Chars(const unsigned int _unicode)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if(_unicode < 0x80) // one byte character
|
||||
{
|
||||
result += ((_unicode ) );
|
||||
}
|
||||
else if(_unicode < 0x800) // two byte character
|
||||
{
|
||||
result += ((_unicode >> 6) ) | 0xC0;
|
||||
result += ((_unicode ) & 0x3F) | 0x80;
|
||||
}
|
||||
else if(_unicode < 0xFFFF) // three byte character
|
||||
{
|
||||
result += ((_unicode >> 12) ) | 0xE0;
|
||||
result += ((_unicode >> 6) & 0x3F) | 0x80;
|
||||
result += ((_unicode ) & 0x3F) | 0x80;
|
||||
}
|
||||
else if(_unicode <= 0x1fffff) // four byte character
|
||||
{
|
||||
result += ((_unicode >> 18) ) | 0xF0;
|
||||
result += ((_unicode >> 12) & 0x3F) | 0x80;
|
||||
result += ((_unicode >> 6) & 0x3F) | 0x80;
|
||||
result += ((_unicode ) & 0x3F) | 0x80;
|
||||
}
|
||||
else
|
||||
{
|
||||
// error, invalid unicode
|
||||
result += '?';
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // unicode2Chars
|
||||
|
||||
inline size_t nextCursor(const std::string& _string, const size_t _cursor)
|
||||
{
|
||||
size_t result = _cursor;
|
||||
|
||||
while(result < _string.length())
|
||||
{
|
||||
++result;
|
||||
|
||||
if((_string[result] & 0xC0) != 0x80) // break if current character is not 10xxxxxx
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // nextCursor
|
||||
|
||||
inline size_t prevCursor(const std::string& _string, const size_t _cursor)
|
||||
{
|
||||
size_t result = _cursor;
|
||||
|
||||
while(result > 0)
|
||||
{
|
||||
--result;
|
||||
|
||||
if((_string[result] & 0xC0) != 0x80) // break if current character is not 10xxxxxx
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // prevCursor
|
||||
|
||||
inline size_t moveCursor(const std::string& _string, const size_t _cursor, const int _amount)
|
||||
{
|
||||
size_t result = _cursor;
|
||||
|
||||
if(_amount > 0)
|
||||
{
|
||||
for(int i = 0; i < _amount; ++i)
|
||||
result = nextCursor(_string, result);
|
||||
}
|
||||
else if(_amount < 0)
|
||||
{
|
||||
for(int i = _amount; i < 0; ++i)
|
||||
result = prevCursor(_string, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // moveCursor
|
||||
}
|
||||
|
||||
#endif // ES_CORE_STRING_UTIL_H
|
|
@ -1,9 +1,9 @@
|
|||
#include "components/TextComponent.h"
|
||||
|
||||
#include "utils/StringUtil.h"
|
||||
#include "Log.h"
|
||||
#include "Renderer.h"
|
||||
#include "Settings.h"
|
||||
#include "StringUtil.h"
|
||||
#include "Util.h"
|
||||
|
||||
TextComponent::TextComponent(Window* window) : GuiComponent(window),
|
||||
|
@ -198,7 +198,7 @@ void TextComponent::onTextChanged()
|
|||
|
||||
while(text.size() && size.x() + abbrevSize.x() > mSize.x())
|
||||
{
|
||||
size_t newSize = StringUtil::prevCursor(text, text.size());
|
||||
size_t newSize = Utils::String::prevCursor(text, text.size());
|
||||
text.erase(newSize, text.size() - newSize);
|
||||
size = f->sizeText(text);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include "components/TextEditComponent.h"
|
||||
|
||||
#include "resources/Font.h"
|
||||
#include "utils/StringUtil.h"
|
||||
#include "Renderer.h"
|
||||
#include "StringUtil.h"
|
||||
|
||||
#define TEXT_PADDING_HORIZ 10
|
||||
#define TEXT_PADDING_VERT 2
|
||||
|
@ -60,7 +60,7 @@ void TextEditComponent::textInput(const char* text)
|
|||
{
|
||||
if(mCursor > 0)
|
||||
{
|
||||
size_t newCursor = StringUtil::prevCursor(mText, mCursor);
|
||||
size_t newCursor = Utils::String::prevCursor(mText, mCursor);
|
||||
mText.erase(mText.begin() + newCursor, mText.begin() + mCursor);
|
||||
mCursor = newCursor;
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ void TextEditComponent::updateCursorRepeat(int deltaTime)
|
|||
|
||||
void TextEditComponent::moveCursor(int amt)
|
||||
{
|
||||
mCursor = StringUtil::moveCursor(mText, mCursor, amt);
|
||||
mCursor = Utils::String::moveCursor(mText, mCursor, amt);
|
||||
onCursorChanged();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include "resources/Font.h"
|
||||
|
||||
#include "utils/StringUtil.h"
|
||||
#include "Log.h"
|
||||
#include "Renderer.h"
|
||||
#include "StringUtil.h"
|
||||
#include "Util.h"
|
||||
|
||||
FT_Library Font::sLibrary = NULL;
|
||||
|
@ -448,7 +448,7 @@ Vector2f Font::sizeText(std::string text, float lineSpacing)
|
|||
size_t i = 0;
|
||||
while(i < text.length())
|
||||
{
|
||||
unsigned int character = StringUtil::chars2Unicode(text, i); // advances i
|
||||
unsigned int character = Utils::String::chars2Unicode(text, i); // advances i
|
||||
|
||||
if(character == '\n')
|
||||
{
|
||||
|
@ -541,8 +541,8 @@ Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, size_t s
|
|||
size_t cursor = 0;
|
||||
while(cursor < stop)
|
||||
{
|
||||
unsigned int wrappedCharacter = StringUtil::chars2Unicode(wrappedText, wrapCursor);
|
||||
unsigned int character = StringUtil::chars2Unicode(text, cursor);
|
||||
unsigned int wrappedCharacter = Utils::String::chars2Unicode(wrappedText, wrapCursor);
|
||||
unsigned int character = Utils::String::chars2Unicode(text, cursor);
|
||||
|
||||
if(wrappedCharacter == '\n' && character != '\n')
|
||||
{
|
||||
|
@ -551,7 +551,7 @@ Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, size_t s
|
|||
lineWidth = 0.0f;
|
||||
y += getHeight(lineSpacing);
|
||||
|
||||
cursor = StringUtil::prevCursor(text, cursor); // unconsume
|
||||
cursor = Utils::String::prevCursor(text, cursor); // unconsume
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -609,7 +609,7 @@ TextCache* Font::buildTextCache(const std::string& text, Vector2f offset, unsign
|
|||
size_t cursor = 0;
|
||||
while(cursor < text.length())
|
||||
{
|
||||
unsigned int character = StringUtil::chars2Unicode(text, cursor); // also advances cursor
|
||||
unsigned int character = Utils::String::chars2Unicode(text, cursor); // also advances cursor
|
||||
Glyph* glyph;
|
||||
|
||||
// invalid character
|
||||
|
|
168
es-core/src/utils/FileSystemUtil.cpp
Normal file
168
es-core/src/utils/FileSystemUtil.cpp
Normal file
|
@ -0,0 +1,168 @@
|
|||
#include "utils/FileSystemUtil.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(WIN32)
|
||||
// because windows...
|
||||
#include <direct.h>
|
||||
#define snprintf _snprintf
|
||||
#define mkdir(x,y) _mkdir(x)
|
||||
#endif // WIN32
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
namespace FileSystem
|
||||
{
|
||||
bool createDirectory(const std::string& path)
|
||||
{
|
||||
// don't create if it already exists
|
||||
if(exists(path))
|
||||
return true;
|
||||
|
||||
// convert '\\' to '/'
|
||||
fixSeparators(path);
|
||||
|
||||
// try to create directory
|
||||
if(mkdir(path.c_str(), 0755) == 0)
|
||||
return true;
|
||||
|
||||
// failed to create directory, try to create the parent
|
||||
std::string parent = getParent(path);
|
||||
|
||||
// only try to create parent if it's not identical to path
|
||||
if(parent != path)
|
||||
createDirectory(parent);
|
||||
|
||||
// try to create directory again now that the parent should exist
|
||||
return (mkdir(path.c_str(), 0755) == 0);
|
||||
|
||||
} // createDirectory
|
||||
|
||||
void fixSeparators(const std::string& path)
|
||||
{
|
||||
char* p = nullptr;
|
||||
|
||||
// convert '\\' to '/'
|
||||
for(p = (char*)path.c_str() + 1; *p; ++p)
|
||||
{
|
||||
if(*p == '\\')
|
||||
*p = '/';
|
||||
}
|
||||
|
||||
} // fixSeparators
|
||||
|
||||
std::string escapePath(const std::string& path)
|
||||
{
|
||||
|
||||
#ifdef WIN32
|
||||
// windows escapes stuff by just putting everything in quotes
|
||||
return '"' + path + '"';
|
||||
#else // WIN32
|
||||
// insert a backslash before most characters that would mess up a bash path
|
||||
std::string escapedPath = path;
|
||||
const char* invalidChars = "\\ '\"!$^&*(){}[]?;<>";
|
||||
const char* invalidChar = invalidChars;
|
||||
|
||||
while(*invalidChar)
|
||||
{
|
||||
for(size_t i = 0; i < escapedPath.length(); ++i)
|
||||
{
|
||||
if(escapedPath[i] == *invalidChar)
|
||||
{
|
||||
escapedPath.insert(i, 1, '\\');
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
++invalidChar;
|
||||
}
|
||||
|
||||
return escapedPath;
|
||||
#endif // WIN32
|
||||
|
||||
} // escapePath
|
||||
|
||||
std::string getParent(const std::string& path)
|
||||
{
|
||||
// convert '\\' to '/'
|
||||
fixSeparators(path);
|
||||
|
||||
// make a copy of the path
|
||||
char temp[512];
|
||||
size_t len = snprintf(temp, sizeof(temp), "%s", path.c_str());
|
||||
|
||||
// find last '/' and end the new path
|
||||
while(len > 1)
|
||||
{
|
||||
if(temp[--len] == '/')
|
||||
{
|
||||
temp[len] = 0;
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
// no parent found
|
||||
return path;
|
||||
|
||||
} // getParent
|
||||
|
||||
std::string getFileName(const std::string& path)
|
||||
{
|
||||
// convert '\\' to '/'
|
||||
fixSeparators(path);
|
||||
|
||||
// make a copy of the path
|
||||
char temp[512];
|
||||
size_t len = snprintf(temp, sizeof(temp), "%s", path.c_str());
|
||||
|
||||
// find last '/' and return the filename
|
||||
while(len > 1)
|
||||
{
|
||||
// return "." if this is the end of the path, otherwise return filename
|
||||
if(temp[--len] == '/')
|
||||
return ((temp[len + 1] == 0) ? "." : (temp + len + 1));
|
||||
}
|
||||
|
||||
// no '/' found, entire path is a filename
|
||||
return path;
|
||||
|
||||
} // getFileName
|
||||
|
||||
std::string getStem(const std::string& path)
|
||||
{
|
||||
std::string fileName = getFileName(path);
|
||||
|
||||
// empty fileName
|
||||
if(fileName == ".")
|
||||
return fileName;
|
||||
|
||||
// make a copy of the filename
|
||||
char temp[512];
|
||||
size_t len = snprintf(temp, sizeof(temp), "%s", fileName.c_str());
|
||||
|
||||
// find last '.' and remove the extension
|
||||
while(len > 1)
|
||||
{
|
||||
if(temp[--len] == '.')
|
||||
{
|
||||
temp[len] = 0;
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
// no '.' found, filename has no extension
|
||||
return fileName;
|
||||
|
||||
} // getStem
|
||||
|
||||
bool exists(const std::string& path)
|
||||
{
|
||||
struct stat info;
|
||||
return (stat(path.c_str(), &info) == 0);
|
||||
|
||||
} // exists
|
||||
|
||||
} // FileSystem::
|
||||
|
||||
} // Utils::
|
23
es-core/src/utils/FileSystemUtil.h
Normal file
23
es-core/src/utils/FileSystemUtil.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
#ifndef ES_CORE_FILE_SYSTEM_UTIL_H
|
||||
#define ES_CORE_FILE_SYSTEM_UTIL_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
namespace FileSystem
|
||||
{
|
||||
bool createDirectory(const std::string& path);
|
||||
void fixSeparators(const std::string& path);
|
||||
std::string escapePath(const std::string& path);
|
||||
std::string getParent(const std::string& path);
|
||||
std::string getFileName(const std::string& path);
|
||||
std::string getStem(const std::string& path);
|
||||
bool exists(const std::string& path);
|
||||
|
||||
} // FileSystem::
|
||||
|
||||
} // Utils::
|
||||
|
||||
#endif // ES_CORE_FILE_SYSTEM_UTIL_H
|
156
es-core/src/utils/StringUtil.cpp
Normal file
156
es-core/src/utils/StringUtil.cpp
Normal file
|
@ -0,0 +1,156 @@
|
|||
#include "utils/StringUtil.h"
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
namespace String
|
||||
{
|
||||
unsigned int chars2Unicode(const std::string& _string, size_t& _cursor)
|
||||
{
|
||||
const char& c = _string[_cursor];
|
||||
unsigned int result = '?';
|
||||
|
||||
if((c & 0x80) == 0) // 0xxxxxxx, one byte character
|
||||
{
|
||||
// 0xxxxxxx
|
||||
result = ((_string[_cursor++] ) );
|
||||
}
|
||||
else if((c & 0xE0) == 0xC0) // 110xxxxx, two byte character
|
||||
{
|
||||
// 110xxxxx 10xxxxxx
|
||||
result = ((_string[_cursor++] & 0x1F) << 6) |
|
||||
((_string[_cursor++] & 0x3F) );
|
||||
}
|
||||
else if((c & 0xF0) == 0xE0) // 1110xxxx, three byte character
|
||||
{
|
||||
// 1110xxxx 10xxxxxx 10xxxxxx
|
||||
result = ((_string[_cursor++] & 0x0F) << 12) |
|
||||
((_string[_cursor++] & 0x3F) << 6) |
|
||||
((_string[_cursor++] & 0x3F) );
|
||||
}
|
||||
else if((c & 0xF8) == 0xF0) // 11110xxx, four byte character
|
||||
{
|
||||
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
result = ((_string[_cursor++] & 0x07) << 18) |
|
||||
((_string[_cursor++] & 0x3F) << 12) |
|
||||
((_string[_cursor++] & 0x3F) << 6) |
|
||||
((_string[_cursor++] & 0x3F) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// error, invalid unicode
|
||||
++_cursor;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // chars2Unicode
|
||||
|
||||
std::string unicode2Chars(const unsigned int _unicode)
|
||||
{
|
||||
std::string result;
|
||||
|
||||
if(_unicode < 0x80) // one byte character
|
||||
{
|
||||
result += ((_unicode ) );
|
||||
}
|
||||
else if(_unicode < 0x800) // two byte character
|
||||
{
|
||||
result += ((_unicode >> 6) ) | 0xC0;
|
||||
result += ((_unicode ) & 0x3F) | 0x80;
|
||||
}
|
||||
else if(_unicode < 0xFFFF) // three byte character
|
||||
{
|
||||
result += ((_unicode >> 12) ) | 0xE0;
|
||||
result += ((_unicode >> 6) & 0x3F) | 0x80;
|
||||
result += ((_unicode ) & 0x3F) | 0x80;
|
||||
}
|
||||
else if(_unicode <= 0x1fffff) // four byte character
|
||||
{
|
||||
result += ((_unicode >> 18) ) | 0xF0;
|
||||
result += ((_unicode >> 12) & 0x3F) | 0x80;
|
||||
result += ((_unicode >> 6) & 0x3F) | 0x80;
|
||||
result += ((_unicode ) & 0x3F) | 0x80;
|
||||
}
|
||||
else
|
||||
{
|
||||
// error, invalid unicode
|
||||
result += '?';
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // unicode2Chars
|
||||
|
||||
size_t nextCursor(const std::string& _string, const size_t _cursor)
|
||||
{
|
||||
size_t result = _cursor;
|
||||
|
||||
while(result < _string.length())
|
||||
{
|
||||
++result;
|
||||
|
||||
if((_string[result] & 0xC0) != 0x80) // break if current character is not 10xxxxxx
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // nextCursor
|
||||
|
||||
size_t prevCursor(const std::string& _string, const size_t _cursor)
|
||||
{
|
||||
size_t result = _cursor;
|
||||
|
||||
while(result > 0)
|
||||
{
|
||||
--result;
|
||||
|
||||
if((_string[result] & 0xC0) != 0x80) // break if current character is not 10xxxxxx
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // prevCursor
|
||||
|
||||
size_t moveCursor(const std::string& _string, const size_t _cursor, const int _amount)
|
||||
{
|
||||
size_t result = _cursor;
|
||||
|
||||
if(_amount > 0)
|
||||
{
|
||||
for(int i = 0; i < _amount; ++i)
|
||||
result = nextCursor(_string, result);
|
||||
}
|
||||
else if(_amount < 0)
|
||||
{
|
||||
for(int i = _amount; i < 0; ++i)
|
||||
result = prevCursor(_string, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} // moveCursor
|
||||
|
||||
void trim(std::string& _string)
|
||||
{
|
||||
if(_string.size())
|
||||
{
|
||||
size_t cursorB = 0;
|
||||
size_t cursorE = _string.size();
|
||||
|
||||
while((cursorB < _string.size()) && _string[cursorB] == ' ')
|
||||
++cursorB;
|
||||
|
||||
while((cursorE > 0) && _string[cursorE - 1] == ' ')
|
||||
--cursorE;
|
||||
|
||||
_string.erase(_string.begin() + cursorE, _string.end());
|
||||
_string.erase(_string.begin(), _string.begin() + cursorB);
|
||||
}
|
||||
|
||||
} // trim
|
||||
|
||||
} // String::
|
||||
|
||||
} // Utils::
|
22
es-core/src/utils/StringUtil.h
Normal file
22
es-core/src/utils/StringUtil.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
#ifndef ES_CORE_STRING_UTIL_H
|
||||
#define ES_CORE_STRING_UTIL_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Utils
|
||||
{
|
||||
namespace String
|
||||
{
|
||||
unsigned int chars2Unicode(const std::string& _string, size_t& _cursor);
|
||||
std::string unicode2Chars(const unsigned int _unicode);
|
||||
size_t nextCursor(const std::string& _string, const size_t _cursor);
|
||||
size_t prevCursor(const std::string& _string, const size_t _cursor);
|
||||
size_t moveCursor(const std::string& _string, const size_t _cursor, const int _amount);
|
||||
void trim(std::string& _string);
|
||||
|
||||
} // String::
|
||||
|
||||
} // Utils::
|
||||
|
||||
#endif // ES_CORE_STRING_UTIL_H
|
Loading…
Reference in a new issue