mirror of
https://github.com/docker/build-push-action.git
synced 2024-11-13 21:55:38 +01:00
Merge pull request #218 from docker/dependabot/npm_and_yarn/csv-parse-4.14.0
Bump csv-parse from 4.12.0 to 4.14.0
This commit is contained in:
commit
6e1d94b6b3
319
dist/index.js
generated
vendored
319
dist/index.js
generated
vendored
@ -10817,16 +10817,44 @@ const nl = 10
|
|||||||
const np = 12
|
const np = 12
|
||||||
const cr = 13
|
const cr = 13
|
||||||
const space = 32
|
const space = 32
|
||||||
const bom_utf8 = Buffer.from([239, 187, 191])
|
const boms = {
|
||||||
|
// Note, the following are equals:
|
||||||
|
// Buffer.from("\ufeff")
|
||||||
|
// Buffer.from([239, 187, 191])
|
||||||
|
// Buffer.from('EFBBBF', 'hex')
|
||||||
|
'utf8': Buffer.from([239, 187, 191]),
|
||||||
|
// Note, the following are equals:
|
||||||
|
// Buffer.from "\ufeff", 'utf16le
|
||||||
|
// Buffer.from([255, 254])
|
||||||
|
'utf16le': Buffer.from([255, 254])
|
||||||
|
}
|
||||||
|
|
||||||
class Parser extends Transform {
|
class Parser extends Transform {
|
||||||
constructor(opts = {}){
|
constructor(opts = {}){
|
||||||
super({...{readableObjectMode: true}, ...opts})
|
super({...{readableObjectMode: true}, ...opts, encoding: null})
|
||||||
|
this.__originalOptions = opts
|
||||||
|
this.__normalizeOptions(opts)
|
||||||
|
}
|
||||||
|
__normalizeOptions(opts){
|
||||||
const options = {}
|
const options = {}
|
||||||
// Merge with user options
|
// Merge with user options
|
||||||
for(let opt in opts){
|
for(let opt in opts){
|
||||||
options[underscore(opt)] = opts[opt]
|
options[underscore(opt)] = opts[opt]
|
||||||
}
|
}
|
||||||
|
// Normalize option `encoding`
|
||||||
|
// Note: defined first because other options depends on it
|
||||||
|
// to convert chars/strings into buffers.
|
||||||
|
if(options.encoding === undefined || options.encoding === true){
|
||||||
|
options.encoding = 'utf8'
|
||||||
|
}else if(options.encoding === null || options.encoding === false){
|
||||||
|
options.encoding = null
|
||||||
|
}else if(typeof options.encoding !== 'string' && options.encoding !== null){
|
||||||
|
throw new CsvError('CSV_INVALID_OPTION_ENCODING', [
|
||||||
|
'Invalid option encoding:',
|
||||||
|
'encoding must be a string or null to return a buffer,',
|
||||||
|
`got ${JSON.stringify(options.encoding)}`
|
||||||
|
], options)
|
||||||
|
}
|
||||||
// Normalize option `bom`
|
// Normalize option `bom`
|
||||||
if(options.bom === undefined || options.bom === null || options.bom === false){
|
if(options.bom === undefined || options.bom === null || options.bom === false){
|
||||||
options.bom = false
|
options.bom = false
|
||||||
@ -10834,7 +10862,7 @@ class Parser extends Transform {
|
|||||||
throw new CsvError('CSV_INVALID_OPTION_BOM', [
|
throw new CsvError('CSV_INVALID_OPTION_BOM', [
|
||||||
'Invalid option bom:', 'bom must be true,',
|
'Invalid option bom:', 'bom must be true,',
|
||||||
`got ${JSON.stringify(options.bom)}`
|
`got ${JSON.stringify(options.bom)}`
|
||||||
])
|
], options)
|
||||||
}
|
}
|
||||||
// Normalize option `cast`
|
// Normalize option `cast`
|
||||||
let fnCastField = null
|
let fnCastField = null
|
||||||
@ -10847,7 +10875,7 @@ class Parser extends Transform {
|
|||||||
throw new CsvError('CSV_INVALID_OPTION_CAST', [
|
throw new CsvError('CSV_INVALID_OPTION_CAST', [
|
||||||
'Invalid option cast:', 'cast must be true or a function,',
|
'Invalid option cast:', 'cast must be true or a function,',
|
||||||
`got ${JSON.stringify(options.cast)}`
|
`got ${JSON.stringify(options.cast)}`
|
||||||
])
|
], options)
|
||||||
}
|
}
|
||||||
// Normalize option `cast_date`
|
// Normalize option `cast_date`
|
||||||
if(options.cast_date === undefined || options.cast_date === null || options.cast_date === false || options.cast_date === ''){
|
if(options.cast_date === undefined || options.cast_date === null || options.cast_date === false || options.cast_date === ''){
|
||||||
@ -10861,7 +10889,7 @@ class Parser extends Transform {
|
|||||||
throw new CsvError('CSV_INVALID_OPTION_CAST_DATE', [
|
throw new CsvError('CSV_INVALID_OPTION_CAST_DATE', [
|
||||||
'Invalid option cast_date:', 'cast_date must be true or a function,',
|
'Invalid option cast_date:', 'cast_date must be true or a function,',
|
||||||
`got ${JSON.stringify(options.cast_date)}`
|
`got ${JSON.stringify(options.cast_date)}`
|
||||||
])
|
], options)
|
||||||
}
|
}
|
||||||
// Normalize option `columns`
|
// Normalize option `columns`
|
||||||
let fnFirstLineToHeaders = null
|
let fnFirstLineToHeaders = null
|
||||||
@ -10880,7 +10908,7 @@ class Parser extends Transform {
|
|||||||
'Invalid option columns:',
|
'Invalid option columns:',
|
||||||
'expect an object, a function or true,',
|
'expect an object, a function or true,',
|
||||||
`got ${JSON.stringify(options.columns)}`
|
`got ${JSON.stringify(options.columns)}`
|
||||||
])
|
], options)
|
||||||
}
|
}
|
||||||
// Normalize option `columns_duplicates_to_array`
|
// Normalize option `columns_duplicates_to_array`
|
||||||
if(options.columns_duplicates_to_array === undefined || options.columns_duplicates_to_array === null || options.columns_duplicates_to_array === false){
|
if(options.columns_duplicates_to_array === undefined || options.columns_duplicates_to_array === null || options.columns_duplicates_to_array === false){
|
||||||
@ -10890,21 +10918,21 @@ class Parser extends Transform {
|
|||||||
'Invalid option columns_duplicates_to_array:',
|
'Invalid option columns_duplicates_to_array:',
|
||||||
'expect an boolean,',
|
'expect an boolean,',
|
||||||
`got ${JSON.stringify(options.columns_duplicates_to_array)}`
|
`got ${JSON.stringify(options.columns_duplicates_to_array)}`
|
||||||
])
|
], options)
|
||||||
}
|
}
|
||||||
// Normalize option `comment`
|
// Normalize option `comment`
|
||||||
if(options.comment === undefined || options.comment === null || options.comment === false || options.comment === ''){
|
if(options.comment === undefined || options.comment === null || options.comment === false || options.comment === ''){
|
||||||
options.comment = null
|
options.comment = null
|
||||||
}else{
|
}else{
|
||||||
if(typeof options.comment === 'string'){
|
if(typeof options.comment === 'string'){
|
||||||
options.comment = Buffer.from(options.comment)
|
options.comment = Buffer.from(options.comment, options.encoding)
|
||||||
}
|
}
|
||||||
if(!Buffer.isBuffer(options.comment)){
|
if(!Buffer.isBuffer(options.comment)){
|
||||||
throw new CsvError('CSV_INVALID_OPTION_COMMENT', [
|
throw new CsvError('CSV_INVALID_OPTION_COMMENT', [
|
||||||
'Invalid option comment:',
|
'Invalid option comment:',
|
||||||
'comment must be a buffer or a string,',
|
'comment must be a buffer or a string,',
|
||||||
`got ${JSON.stringify(options.comment)}`
|
`got ${JSON.stringify(options.comment)}`
|
||||||
])
|
], options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Normalize option `delimiter`
|
// Normalize option `delimiter`
|
||||||
@ -10915,39 +10943,35 @@ class Parser extends Transform {
|
|||||||
'Invalid option delimiter:',
|
'Invalid option delimiter:',
|
||||||
'delimiter must be a non empty string or buffer or array of string|buffer,',
|
'delimiter must be a non empty string or buffer or array of string|buffer,',
|
||||||
`got ${delimiter_json}`
|
`got ${delimiter_json}`
|
||||||
])
|
], options)
|
||||||
}
|
}
|
||||||
options.delimiter = options.delimiter.map(function(delimiter){
|
options.delimiter = options.delimiter.map(function(delimiter){
|
||||||
if(delimiter === undefined || delimiter === null || delimiter === false){
|
if(delimiter === undefined || delimiter === null || delimiter === false){
|
||||||
return Buffer.from(',')
|
return Buffer.from(',', options.encoding)
|
||||||
}
|
}
|
||||||
if(typeof delimiter === 'string'){
|
if(typeof delimiter === 'string'){
|
||||||
delimiter = Buffer.from(delimiter)
|
delimiter = Buffer.from(delimiter, options.encoding)
|
||||||
}
|
}
|
||||||
if( !Buffer.isBuffer(delimiter) || delimiter.length === 0){
|
if( !Buffer.isBuffer(delimiter) || delimiter.length === 0){
|
||||||
throw new CsvError('CSV_INVALID_OPTION_DELIMITER', [
|
throw new CsvError('CSV_INVALID_OPTION_DELIMITER', [
|
||||||
'Invalid option delimiter:',
|
'Invalid option delimiter:',
|
||||||
'delimiter must be a non empty string or buffer or array of string|buffer,',
|
'delimiter must be a non empty string or buffer or array of string|buffer,',
|
||||||
`got ${delimiter_json}`
|
`got ${delimiter_json}`
|
||||||
])
|
], options)
|
||||||
}
|
}
|
||||||
return delimiter
|
return delimiter
|
||||||
})
|
})
|
||||||
// Normalize option `escape`
|
// Normalize option `escape`
|
||||||
if(options.escape === undefined || options.escape === true){
|
if(options.escape === undefined || options.escape === true){
|
||||||
options.escape = Buffer.from('"')
|
options.escape = Buffer.from('"', options.encoding)
|
||||||
}else if(typeof options.escape === 'string'){
|
}else if(typeof options.escape === 'string'){
|
||||||
options.escape = Buffer.from(options.escape)
|
options.escape = Buffer.from(options.escape, options.encoding)
|
||||||
}else if (options.escape === null || options.escape === false){
|
}else if (options.escape === null || options.escape === false){
|
||||||
options.escape = null
|
options.escape = null
|
||||||
}
|
}
|
||||||
if(options.escape !== null){
|
if(options.escape !== null){
|
||||||
if(!Buffer.isBuffer(options.escape)){
|
if(!Buffer.isBuffer(options.escape)){
|
||||||
throw new Error(`Invalid Option: escape must be a buffer, a string or a boolean, got ${JSON.stringify(options.escape)}`)
|
throw new Error(`Invalid Option: escape must be a buffer, a string or a boolean, got ${JSON.stringify(options.escape)}`)
|
||||||
}else if(options.escape.length !== 1){
|
|
||||||
throw new Error(`Invalid Option Length: escape must be one character, got ${options.escape.length}`)
|
|
||||||
}else{
|
|
||||||
options.escape = options.escape[0]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Normalize option `from`
|
// Normalize option `from`
|
||||||
@ -11003,7 +11027,11 @@ class Parser extends Transform {
|
|||||||
if(options.objname.length === 0){
|
if(options.objname.length === 0){
|
||||||
throw new Error(`Invalid Option: objname must be a non empty buffer`)
|
throw new Error(`Invalid Option: objname must be a non empty buffer`)
|
||||||
}
|
}
|
||||||
options.objname = options.objname.toString()
|
if(options.encoding === null){
|
||||||
|
// Don't call `toString`, leave objname as a buffer
|
||||||
|
}else{
|
||||||
|
options.objname = options.objname.toString(options.encoding)
|
||||||
|
}
|
||||||
}else if(typeof options.objname === 'string'){
|
}else if(typeof options.objname === 'string'){
|
||||||
if(options.objname.length === 0){
|
if(options.objname.length === 0){
|
||||||
throw new Error(`Invalid Option: objname must be a non empty string`)
|
throw new Error(`Invalid Option: objname must be a non empty string`)
|
||||||
@ -11020,23 +11048,19 @@ class Parser extends Transform {
|
|||||||
'Invalid option `on_record`:',
|
'Invalid option `on_record`:',
|
||||||
'expect a function,',
|
'expect a function,',
|
||||||
`got ${JSON.stringify(options.on_record)}`
|
`got ${JSON.stringify(options.on_record)}`
|
||||||
])
|
], options)
|
||||||
}
|
}
|
||||||
// Normalize option `quote`
|
// Normalize option `quote`
|
||||||
if(options.quote === null || options.quote === false || options.quote === ''){
|
if(options.quote === null || options.quote === false || options.quote === ''){
|
||||||
options.quote = null
|
options.quote = null
|
||||||
}else{
|
}else{
|
||||||
if(options.quote === undefined || options.quote === true){
|
if(options.quote === undefined || options.quote === true){
|
||||||
options.quote = Buffer.from('"')
|
options.quote = Buffer.from('"', options.encoding)
|
||||||
}else if(typeof options.quote === 'string'){
|
}else if(typeof options.quote === 'string'){
|
||||||
options.quote = Buffer.from(options.quote)
|
options.quote = Buffer.from(options.quote, options.encoding)
|
||||||
}
|
}
|
||||||
if(!Buffer.isBuffer(options.quote)){
|
if(!Buffer.isBuffer(options.quote)){
|
||||||
throw new Error(`Invalid Option: quote must be a buffer or a string, got ${JSON.stringify(options.quote)}`)
|
throw new Error(`Invalid Option: quote must be a buffer or a string, got ${JSON.stringify(options.quote)}`)
|
||||||
}else if(options.quote.length !== 1){
|
|
||||||
throw new Error(`Invalid Option Length: quote must be one character, got ${options.quote.length}`)
|
|
||||||
}else{
|
|
||||||
options.quote = options.quote[0]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Normalize option `raw`
|
// Normalize option `raw`
|
||||||
@ -11053,7 +11077,7 @@ class Parser extends Transform {
|
|||||||
}
|
}
|
||||||
options.record_delimiter = options.record_delimiter.map( function(rd){
|
options.record_delimiter = options.record_delimiter.map( function(rd){
|
||||||
if(typeof rd === 'string'){
|
if(typeof rd === 'string'){
|
||||||
rd = Buffer.from(rd)
|
rd = Buffer.from(rd, options.encoding)
|
||||||
}
|
}
|
||||||
return rd
|
return rd
|
||||||
})
|
})
|
||||||
@ -11182,13 +11206,24 @@ class Parser extends Transform {
|
|||||||
bomSkipped: false,
|
bomSkipped: false,
|
||||||
castField: fnCastField,
|
castField: fnCastField,
|
||||||
commenting: false,
|
commenting: false,
|
||||||
|
// Current error encountered by a record
|
||||||
|
error: undefined,
|
||||||
enabled: options.from_line === 1,
|
enabled: options.from_line === 1,
|
||||||
escaping: false,
|
escaping: false,
|
||||||
escapeIsQuote: options.escape === options.quote,
|
// escapeIsQuote: options.escape === options.quote,
|
||||||
|
escapeIsQuote: Buffer.isBuffer(options.escape) && Buffer.isBuffer(options.quote) && Buffer.compare(options.escape, options.quote) === 0,
|
||||||
expectedRecordLength: options.columns === null ? 0 : options.columns.length,
|
expectedRecordLength: options.columns === null ? 0 : options.columns.length,
|
||||||
field: new ResizeableBuffer(20),
|
field: new ResizeableBuffer(20),
|
||||||
firstLineToHeaders: fnFirstLineToHeaders,
|
firstLineToHeaders: fnFirstLineToHeaders,
|
||||||
info: Object.assign({}, this.info),
|
info: Object.assign({}, this.info),
|
||||||
|
needMoreDataSize: Math.max(
|
||||||
|
// Skip if the remaining buffer smaller than comment
|
||||||
|
options.comment !== null ? options.comment.length : 0,
|
||||||
|
// Skip if the remaining buffer can be delimiter
|
||||||
|
...options.delimiter.map( (delimiter) => delimiter.length),
|
||||||
|
// Skip if the remaining buffer can be escape sequence
|
||||||
|
options.quote !== null ? options.quote.length : 0,
|
||||||
|
),
|
||||||
previousBuf: undefined,
|
previousBuf: undefined,
|
||||||
quoting: false,
|
quoting: false,
|
||||||
stop: false,
|
stop: false,
|
||||||
@ -11197,7 +11232,7 @@ class Parser extends Transform {
|
|||||||
recordHasError: false,
|
recordHasError: false,
|
||||||
record_length: 0,
|
record_length: 0,
|
||||||
recordDelimiterMaxLength: options.record_delimiter.length === 0 ? 2 : Math.max(...options.record_delimiter.map( (v) => v.length)),
|
recordDelimiterMaxLength: options.record_delimiter.length === 0 ? 2 : Math.max(...options.record_delimiter.map( (v) => v.length)),
|
||||||
trimChars: [Buffer.from(' ')[0], Buffer.from('\t')[0]],
|
trimChars: [Buffer.from(' ', options.encoding)[0], Buffer.from('\t', options.encoding)[0]],
|
||||||
wasQuoting: false,
|
wasQuoting: false,
|
||||||
wasRowDelimiter: false
|
wasRowDelimiter: false
|
||||||
}
|
}
|
||||||
@ -11251,11 +11286,15 @@ class Parser extends Transform {
|
|||||||
this.state.previousBuf = buf
|
this.state.previousBuf = buf
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// skip BOM detect because data length < 3
|
|
||||||
}else{
|
}else{
|
||||||
if(bom_utf8.compare(buf, 0, 3) === 0){
|
for(let encoding in boms){
|
||||||
// Skip BOM
|
if(boms[encoding].compare(buf, 0, boms[encoding].length) === 0){
|
||||||
buf = buf.slice(3)
|
// Skip BOM
|
||||||
|
buf = buf.slice(boms[encoding].length)
|
||||||
|
// Renormalize original options with the new encoding
|
||||||
|
this.__normalizeOptions({...this.__originalOptions, encoding: encoding})
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.state.bomSkipped = true
|
this.state.bomSkipped = true
|
||||||
}
|
}
|
||||||
@ -11301,35 +11340,37 @@ class Parser extends Transform {
|
|||||||
}else{
|
}else{
|
||||||
// Escape is only active inside quoted fields
|
// Escape is only active inside quoted fields
|
||||||
// We are quoting, the char is an escape chr and there is a chr to escape
|
// We are quoting, the char is an escape chr and there is a chr to escape
|
||||||
if(escape !== null && this.state.quoting === true && chr === escape && pos + 1 < bufLen){
|
// if(escape !== null && this.state.quoting === true && chr === escape && pos + 1 < bufLen){
|
||||||
|
if(escape !== null && this.state.quoting === true && this.__isEscape(buf, pos, chr) && pos + escape.length < bufLen){
|
||||||
if(escapeIsQuote){
|
if(escapeIsQuote){
|
||||||
if(buf[pos+1] === quote){
|
if(this.__isQuote(buf, pos+escape.length)){
|
||||||
this.state.escaping = true
|
this.state.escaping = true
|
||||||
|
pos += escape.length - 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
this.state.escaping = true
|
this.state.escaping = true
|
||||||
|
pos += escape.length - 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Not currently escaping and chr is a quote
|
// Not currently escaping and chr is a quote
|
||||||
// TODO: need to compare bytes instead of single char
|
// TODO: need to compare bytes instead of single char
|
||||||
if(this.state.commenting === false && chr === quote){
|
if(this.state.commenting === false && this.__isQuote(buf, pos)){
|
||||||
if(this.state.quoting === true){
|
if(this.state.quoting === true){
|
||||||
const nextChr = buf[pos+1]
|
const nextChr = buf[pos+quote.length]
|
||||||
const isNextChrTrimable = rtrim && this.__isCharTrimable(nextChr)
|
const isNextChrTrimable = rtrim && this.__isCharTrimable(nextChr)
|
||||||
// const isNextChrComment = nextChr === comment
|
const isNextChrComment = comment !== null && this.__compareBytes(comment, buf, pos+quote.length, nextChr)
|
||||||
const isNextChrComment = comment !== null && this.__compareBytes(comment, buf, pos+1, nextChr)
|
const isNextChrDelimiter = this.__isDelimiter(buf, pos+quote.length, nextChr)
|
||||||
const isNextChrDelimiter = this.__isDelimiter(nextChr, buf, pos+1)
|
const isNextChrRowDelimiter = record_delimiter.length === 0 ? this.__autoDiscoverRowDelimiter(buf, pos+quote.length) : this.__isRecordDelimiter(nextChr, buf, pos+quote.length)
|
||||||
const isNextChrRowDelimiter = record_delimiter.length === 0 ? this.__autoDiscoverRowDelimiter(buf, pos+1) : this.__isRecordDelimiter(nextChr, buf, pos+1)
|
|
||||||
// Escape a quote
|
// Escape a quote
|
||||||
// Treat next char as a regular character
|
// Treat next char as a regular character
|
||||||
// TODO: need to compare bytes instead of single char
|
if(escape !== null && this.__isEscape(buf, pos, chr) && this.__isQuote(buf, pos + escape.length)){
|
||||||
if(escape !== null && chr === escape && nextChr === quote){
|
pos += escape.length - 1
|
||||||
pos++
|
|
||||||
}else if(!nextChr || isNextChrDelimiter || isNextChrRowDelimiter || isNextChrComment || isNextChrTrimable){
|
}else if(!nextChr || isNextChrDelimiter || isNextChrRowDelimiter || isNextChrComment || isNextChrTrimable){
|
||||||
this.state.quoting = false
|
this.state.quoting = false
|
||||||
this.state.wasQuoting = true
|
this.state.wasQuoting = true
|
||||||
|
pos += quote.length - 1
|
||||||
continue
|
continue
|
||||||
}else if(relax === false){
|
}else if(relax === false){
|
||||||
const err = this.__error(
|
const err = this.__error(
|
||||||
@ -11339,14 +11380,14 @@ class Parser extends Transform {
|
|||||||
`at line ${this.info.lines}`,
|
`at line ${this.info.lines}`,
|
||||||
'instead of delimiter, row delimiter, trimable character',
|
'instead of delimiter, row delimiter, trimable character',
|
||||||
'(if activated) or comment',
|
'(if activated) or comment',
|
||||||
], this.__context())
|
], this.options, this.__context())
|
||||||
)
|
)
|
||||||
if(err !== undefined) return err
|
if(err !== undefined) return err
|
||||||
}else{
|
}else{
|
||||||
this.state.quoting = false
|
this.state.quoting = false
|
||||||
this.state.wasQuoting = true
|
this.state.wasQuoting = true
|
||||||
// continue
|
|
||||||
this.state.field.prepend(quote)
|
this.state.field.prepend(quote)
|
||||||
|
pos += quote.length - 1
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
if(this.state.field.length !== 0){
|
if(this.state.field.length !== 0){
|
||||||
@ -11356,7 +11397,7 @@ class Parser extends Transform {
|
|||||||
new CsvError('INVALID_OPENING_QUOTE', [
|
new CsvError('INVALID_OPENING_QUOTE', [
|
||||||
'Invalid Opening Quote:',
|
'Invalid Opening Quote:',
|
||||||
`a quote is found inside a field at line ${this.info.lines}`,
|
`a quote is found inside a field at line ${this.info.lines}`,
|
||||||
], this.__context(), {
|
], this.options, this.__context(), {
|
||||||
field: this.state.field,
|
field: this.state.field,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -11364,6 +11405,7 @@ class Parser extends Transform {
|
|||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
this.state.quoting = true
|
this.state.quoting = true
|
||||||
|
pos += quote.length - 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -11414,7 +11456,7 @@ class Parser extends Transform {
|
|||||||
this.state.commenting = true
|
this.state.commenting = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let delimiterLength = this.__isDelimiter(chr, buf, pos)
|
let delimiterLength = this.__isDelimiter(buf, pos, chr)
|
||||||
if(delimiterLength !== 0){
|
if(delimiterLength !== 0){
|
||||||
const errField = this.__onField()
|
const errField = this.__onField()
|
||||||
if(errField !== undefined) return errField
|
if(errField !== undefined) return errField
|
||||||
@ -11431,7 +11473,7 @@ class Parser extends Transform {
|
|||||||
'record exceed the maximum number of tolerated bytes',
|
'record exceed the maximum number of tolerated bytes',
|
||||||
`of ${max_record_size}`,
|
`of ${max_record_size}`,
|
||||||
`at line ${this.info.lines}`,
|
`at line ${this.info.lines}`,
|
||||||
], this.__context())
|
], this.options, this.__context())
|
||||||
)
|
)
|
||||||
if(err !== undefined) return err
|
if(err !== undefined) return err
|
||||||
}
|
}
|
||||||
@ -11448,7 +11490,7 @@ class Parser extends Transform {
|
|||||||
'Invalid Closing Quote:',
|
'Invalid Closing Quote:',
|
||||||
'found non trimable byte after quote',
|
'found non trimable byte after quote',
|
||||||
`at line ${this.info.lines}`,
|
`at line ${this.info.lines}`,
|
||||||
], this.__context())
|
], this.options, this.__context())
|
||||||
)
|
)
|
||||||
if(err !== undefined) return err
|
if(err !== undefined) return err
|
||||||
}
|
}
|
||||||
@ -11460,7 +11502,7 @@ class Parser extends Transform {
|
|||||||
new CsvError('CSV_QUOTE_NOT_CLOSED', [
|
new CsvError('CSV_QUOTE_NOT_CLOSED', [
|
||||||
'Quote Not Closed:',
|
'Quote Not Closed:',
|
||||||
`the parsing is finished with an opening quote at line ${this.info.lines}`,
|
`the parsing is finished with an opening quote at line ${this.info.lines}`,
|
||||||
], this.__context())
|
], this.options, this.__context())
|
||||||
)
|
)
|
||||||
if(err !== undefined) return err
|
if(err !== undefined) return err
|
||||||
}else{
|
}else{
|
||||||
@ -11489,7 +11531,7 @@ class Parser extends Transform {
|
|||||||
return chr === space || chr === tab || chr === cr || chr === nl || chr === np
|
return chr === space || chr === tab || chr === cr || chr === nl || chr === np
|
||||||
}
|
}
|
||||||
__onRow(){
|
__onRow(){
|
||||||
const {columns, columns_duplicates_to_array, info, from, relax_column_count, relax_column_count_less, relax_column_count_more, raw, skip_lines_with_empty_values} = this.options
|
const {columns, columns_duplicates_to_array, encoding, info, from, relax_column_count, relax_column_count_less, relax_column_count_more, raw, skip_lines_with_empty_values} = this.options
|
||||||
const {enabled, record} = this.state
|
const {enabled, record} = this.state
|
||||||
if(enabled === false){
|
if(enabled === false){
|
||||||
return this.__resetRow()
|
return this.__resetRow()
|
||||||
@ -11507,35 +11549,38 @@ class Parser extends Transform {
|
|||||||
this.state.expectedRecordLength = recordLength
|
this.state.expectedRecordLength = recordLength
|
||||||
}
|
}
|
||||||
if(recordLength !== this.state.expectedRecordLength){
|
if(recordLength !== this.state.expectedRecordLength){
|
||||||
|
const err = columns === false ?
|
||||||
|
this.__error(
|
||||||
|
// Todo: rename CSV_INCONSISTENT_RECORD_LENGTH to
|
||||||
|
// CSV_RECORD_INCONSISTENT_FIELDS_LENGTH
|
||||||
|
new CsvError('CSV_INCONSISTENT_RECORD_LENGTH', [
|
||||||
|
'Invalid Record Length:',
|
||||||
|
`expect ${this.state.expectedRecordLength},`,
|
||||||
|
`got ${recordLength} on line ${this.info.lines}`,
|
||||||
|
], this.options, this.__context(), {
|
||||||
|
record: record,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
:
|
||||||
|
this.__error(
|
||||||
|
// Todo: rename CSV_RECORD_DONT_MATCH_COLUMNS_LENGTH to
|
||||||
|
// CSV_RECORD_INCONSISTENT_COLUMNS
|
||||||
|
new CsvError('CSV_RECORD_DONT_MATCH_COLUMNS_LENGTH', [
|
||||||
|
'Invalid Record Length:',
|
||||||
|
`columns length is ${columns.length},`, // rename columns
|
||||||
|
`got ${recordLength} on line ${this.info.lines}`,
|
||||||
|
], this.options, this.__context(), {
|
||||||
|
record: record,
|
||||||
|
})
|
||||||
|
)
|
||||||
if(relax_column_count === true ||
|
if(relax_column_count === true ||
|
||||||
(relax_column_count_less === true && recordLength < this.state.expectedRecordLength) ||
|
(relax_column_count_less === true && recordLength < this.state.expectedRecordLength) ||
|
||||||
(relax_column_count_more === true && recordLength > this.state.expectedRecordLength) ){
|
(relax_column_count_more === true && recordLength > this.state.expectedRecordLength) ){
|
||||||
this.info.invalid_field_length++
|
this.info.invalid_field_length++
|
||||||
}else{
|
this.state.error = err
|
||||||
if(columns === false){
|
// Error is undefined with skip_lines_with_error
|
||||||
const err = this.__error(
|
}else if(err !== undefined){
|
||||||
new CsvError('CSV_INCONSISTENT_RECORD_LENGTH', [
|
return err
|
||||||
'Invalid Record Length:',
|
|
||||||
`expect ${this.state.expectedRecordLength},`,
|
|
||||||
`got ${recordLength} on line ${this.info.lines}`,
|
|
||||||
], this.__context(), {
|
|
||||||
record: record,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
if(err !== undefined) return err
|
|
||||||
}else{
|
|
||||||
const err = this.__error(
|
|
||||||
// CSV_INVALID_RECORD_LENGTH_DONT_MATCH_COLUMNS
|
|
||||||
new CsvError('CSV_RECORD_DONT_MATCH_COLUMNS_LENGTH', [
|
|
||||||
'Invalid Record Length:',
|
|
||||||
`columns length is ${columns.length},`, // rename columns
|
|
||||||
`got ${recordLength} on line ${this.info.lines}`,
|
|
||||||
], this.__context(), {
|
|
||||||
record: record,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
if(err !== undefined) return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(skip_lines_with_empty_values === true){
|
if(skip_lines_with_empty_values === true){
|
||||||
@ -11556,7 +11601,6 @@ class Parser extends Transform {
|
|||||||
// Transform record array to an object
|
// Transform record array to an object
|
||||||
for(let i = 0, l = record.length; i < l; i++){
|
for(let i = 0, l = record.length; i < l; i++){
|
||||||
if(columns[i] === undefined || columns[i].disabled) continue
|
if(columns[i] === undefined || columns[i].disabled) continue
|
||||||
// obj[columns[i].name] = record[i]
|
|
||||||
// Turn duplicate columns into an array
|
// Turn duplicate columns into an array
|
||||||
if (columns_duplicates_to_array === true && obj[columns[i].name]) {
|
if (columns_duplicates_to_array === true && obj[columns[i].name]) {
|
||||||
if (Array.isArray(obj[columns[i].name])) {
|
if (Array.isArray(obj[columns[i].name])) {
|
||||||
@ -11573,7 +11617,7 @@ class Parser extends Transform {
|
|||||||
if(raw === true || info === true){
|
if(raw === true || info === true){
|
||||||
const err = this.__push(Object.assign(
|
const err = this.__push(Object.assign(
|
||||||
{record: obj},
|
{record: obj},
|
||||||
(raw === true ? {raw: this.state.rawBuffer.toString()}: {}),
|
(raw === true ? {raw: this.state.rawBuffer.toString(encoding)}: {}),
|
||||||
(info === true ? {info: this.state.info}: {})
|
(info === true ? {info: this.state.info}: {})
|
||||||
))
|
))
|
||||||
if(err){
|
if(err){
|
||||||
@ -11589,7 +11633,7 @@ class Parser extends Transform {
|
|||||||
if(raw === true || info === true){
|
if(raw === true || info === true){
|
||||||
const err = this.__push(Object.assign(
|
const err = this.__push(Object.assign(
|
||||||
{record: [obj[objname], obj]},
|
{record: [obj[objname], obj]},
|
||||||
raw === true ? {raw: this.state.rawBuffer.toString()}: {},
|
raw === true ? {raw: this.state.rawBuffer.toString(encoding)}: {},
|
||||||
info === true ? {info: this.state.info}: {}
|
info === true ? {info: this.state.info}: {}
|
||||||
))
|
))
|
||||||
if(err){
|
if(err){
|
||||||
@ -11606,7 +11650,7 @@ class Parser extends Transform {
|
|||||||
if(raw === true || info === true){
|
if(raw === true || info === true){
|
||||||
const err = this.__push(Object.assign(
|
const err = this.__push(Object.assign(
|
||||||
{record: record},
|
{record: record},
|
||||||
raw === true ? {raw: this.state.rawBuffer.toString()}: {},
|
raw === true ? {raw: this.state.rawBuffer.toString(encoding)}: {},
|
||||||
info === true ? {info: this.state.info}: {}
|
info === true ? {info: this.state.info}: {}
|
||||||
))
|
))
|
||||||
if(err){
|
if(err){
|
||||||
@ -11632,7 +11676,7 @@ class Parser extends Transform {
|
|||||||
'Invalid Column Mapping:',
|
'Invalid Column Mapping:',
|
||||||
'expect an array from column function,',
|
'expect an array from column function,',
|
||||||
`got ${JSON.stringify(headers)}`
|
`got ${JSON.stringify(headers)}`
|
||||||
], this.__context(), {
|
], this.options, this.__context(), {
|
||||||
headers: headers,
|
headers: headers,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -11650,17 +11694,18 @@ class Parser extends Transform {
|
|||||||
if(this.options.raw === true){
|
if(this.options.raw === true){
|
||||||
this.state.rawBuffer.reset()
|
this.state.rawBuffer.reset()
|
||||||
}
|
}
|
||||||
|
this.state.error = undefined
|
||||||
this.state.record = []
|
this.state.record = []
|
||||||
this.state.record_length = 0
|
this.state.record_length = 0
|
||||||
}
|
}
|
||||||
__onField(){
|
__onField(){
|
||||||
const {cast, rtrim, max_record_size} = this.options
|
const {cast, encoding, rtrim, max_record_size} = this.options
|
||||||
const {enabled, wasQuoting} = this.state
|
const {enabled, wasQuoting} = this.state
|
||||||
// Short circuit for the from_line options
|
// Short circuit for the from_line options
|
||||||
if(enabled === false){ /* this.options.columns !== true && */
|
if(enabled === false){ /* this.options.columns !== true && */
|
||||||
return this.__resetField()
|
return this.__resetField()
|
||||||
}
|
}
|
||||||
let field = this.state.field.toString()
|
let field = this.state.field.toString(encoding)
|
||||||
if(rtrim === true && wasQuoting === false){
|
if(rtrim === true && wasQuoting === false){
|
||||||
field = field.trimRight()
|
field = field.trimRight()
|
||||||
}
|
}
|
||||||
@ -11727,38 +11772,30 @@ class Parser extends Transform {
|
|||||||
__isFloat(value){
|
__isFloat(value){
|
||||||
return (value - parseFloat( value ) + 1) >= 0 // Borrowed from jquery
|
return (value - parseFloat( value ) + 1) >= 0 // Borrowed from jquery
|
||||||
}
|
}
|
||||||
__compareBytes(sourceBuf, targetBuf, pos, firtByte){
|
__compareBytes(sourceBuf, targetBuf, targetPos, firstByte){
|
||||||
if(sourceBuf[0] !== firtByte) return 0
|
if(sourceBuf[0] !== firstByte) return 0
|
||||||
const sourceLength = sourceBuf.length
|
const sourceLength = sourceBuf.length
|
||||||
for(let i = 1; i < sourceLength; i++){
|
for(let i = 1; i < sourceLength; i++){
|
||||||
if(sourceBuf[i] !== targetBuf[pos+i]) return 0
|
if(sourceBuf[i] !== targetBuf[targetPos+i]) return 0
|
||||||
}
|
}
|
||||||
return sourceLength
|
return sourceLength
|
||||||
}
|
}
|
||||||
__needMoreData(i, bufLen, end){
|
__needMoreData(i, bufLen, end){
|
||||||
if(end){
|
if(end) return false
|
||||||
return false
|
const {quote} = this.options
|
||||||
}
|
const {quoting, needMoreDataSize, recordDelimiterMaxLength} = this.state
|
||||||
const {comment, delimiter} = this.options
|
|
||||||
const {quoting, recordDelimiterMaxLength} = this.state
|
|
||||||
const numOfCharLeft = bufLen - i - 1
|
const numOfCharLeft = bufLen - i - 1
|
||||||
const requiredLength = Math.max(
|
const requiredLength = Math.max(
|
||||||
// Skip if the remaining buffer smaller than comment
|
needMoreDataSize,
|
||||||
comment ? comment.length : 0,
|
// Skip if the remaining buffer smaller than record delimiter
|
||||||
// Skip if the remaining buffer smaller than row delimiter
|
|
||||||
recordDelimiterMaxLength,
|
recordDelimiterMaxLength,
|
||||||
// Skip if the remaining buffer can be row delimiter following the closing quote
|
// Skip if the remaining buffer can be row delimiter following the closing quote
|
||||||
// 1 is for quote.length
|
// 1 is for quote.length
|
||||||
quoting ? (1 + recordDelimiterMaxLength) : 0,
|
quoting ? (quote.length + recordDelimiterMaxLength) : 0,
|
||||||
// Skip if the remaining buffer can be delimiter
|
|
||||||
delimiter.length,
|
|
||||||
// Skip if the remaining buffer can be escape sequence
|
|
||||||
// 1 is for escape.length
|
|
||||||
1
|
|
||||||
)
|
)
|
||||||
return numOfCharLeft < requiredLength
|
return numOfCharLeft < requiredLength
|
||||||
}
|
}
|
||||||
__isDelimiter(chr, buf, pos){
|
__isDelimiter(buf, pos, chr){
|
||||||
const {delimiter} = this.options
|
const {delimiter} = this.options
|
||||||
loop1: for(let i = 0; i < delimiter.length; i++){
|
loop1: for(let i = 0; i < delimiter.length; i++){
|
||||||
const del = delimiter[i]
|
const del = delimiter[i]
|
||||||
@ -11789,20 +11826,46 @@ class Parser extends Transform {
|
|||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
__isEscape(buf, pos, chr){
|
||||||
|
const {escape} = this.options
|
||||||
|
if(escape === null) return false
|
||||||
|
const l = escape.length
|
||||||
|
if(escape[0] === chr){
|
||||||
|
for(let i = 0; i < l; i++){
|
||||||
|
if(escape[i] !== buf[pos+i]){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
__isQuote(buf, pos){
|
||||||
|
const {quote} = this.options
|
||||||
|
if(quote === null) return false
|
||||||
|
const l = quote.length
|
||||||
|
for(let i = 0; i < l; i++){
|
||||||
|
if(quote[i] !== buf[pos+i]){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
__autoDiscoverRowDelimiter(buf, pos){
|
__autoDiscoverRowDelimiter(buf, pos){
|
||||||
|
const {encoding} = this.options
|
||||||
const chr = buf[pos]
|
const chr = buf[pos]
|
||||||
if(chr === cr){
|
if(chr === cr){
|
||||||
if(buf[pos+1] === nl){
|
if(buf[pos+1] === nl){
|
||||||
this.options.record_delimiter.push(Buffer.from('\r\n'))
|
this.options.record_delimiter.push(Buffer.from('\r\n', encoding))
|
||||||
this.state.recordDelimiterMaxLength = 2
|
this.state.recordDelimiterMaxLength = 2
|
||||||
return 2
|
return 2
|
||||||
}else{
|
}else{
|
||||||
this.options.record_delimiter.push(Buffer.from('\r'))
|
this.options.record_delimiter.push(Buffer.from('\r', encoding))
|
||||||
this.state.recordDelimiterMaxLength = 1
|
this.state.recordDelimiterMaxLength = 1
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}else if(chr === nl){
|
}else if(chr === nl){
|
||||||
this.options.record_delimiter.push(Buffer.from('\n'))
|
this.options.record_delimiter.push(Buffer.from('\n', encoding))
|
||||||
this.state.recordDelimiterMaxLength = 1
|
this.state.recordDelimiterMaxLength = 1
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
@ -11830,6 +11893,7 @@ class Parser extends Transform {
|
|||||||
) :
|
) :
|
||||||
this.state.record.length,
|
this.state.record.length,
|
||||||
empty_lines: this.info.empty_lines,
|
empty_lines: this.info.empty_lines,
|
||||||
|
error: this.state.error,
|
||||||
header: columns === true,
|
header: columns === true,
|
||||||
index: this.state.record.length,
|
index: this.state.record.length,
|
||||||
invalid_field_length: this.info.invalid_field_length,
|
invalid_field_length: this.info.invalid_field_length,
|
||||||
@ -11855,7 +11919,7 @@ const parse = function(){
|
|||||||
throw new CsvError('CSV_INVALID_ARGUMENT', [
|
throw new CsvError('CSV_INVALID_ARGUMENT', [
|
||||||
'Invalid argument:',
|
'Invalid argument:',
|
||||||
`got ${JSON.stringify(argument)} at index ${i}`
|
`got ${JSON.stringify(argument)} at index ${i}`
|
||||||
])
|
], this.options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const parser = new Parser(options)
|
const parser = new Parser(options)
|
||||||
@ -11894,7 +11958,7 @@ const parse = function(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CsvError extends Error {
|
class CsvError extends Error {
|
||||||
constructor(code, message, ...contexts) {
|
constructor(code, message, options, ...contexts) {
|
||||||
if(Array.isArray(message)) message = message.join(' ')
|
if(Array.isArray(message)) message = message.join(' ')
|
||||||
super(message)
|
super(message)
|
||||||
if(Error.captureStackTrace !== undefined){
|
if(Error.captureStackTrace !== undefined){
|
||||||
@ -11904,7 +11968,7 @@ class CsvError extends Error {
|
|||||||
for(const context of contexts){
|
for(const context of contexts){
|
||||||
for(const key in context){
|
for(const key in context){
|
||||||
const value = context[key]
|
const value = context[key]
|
||||||
this[key] = Buffer.isBuffer(value) ? value.toString() : value == null ? value : JSON.parse(JSON.stringify(value))
|
this[key] = Buffer.isBuffer(value) ? value.toString(options.encoding) : value == null ? value : JSON.parse(JSON.stringify(value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -13192,13 +13256,28 @@ class ResizeableBuffer{
|
|||||||
this.buf = Buffer.alloc(size)
|
this.buf = Buffer.alloc(size)
|
||||||
}
|
}
|
||||||
prepend(val){
|
prepend(val){
|
||||||
const length = this.length++
|
if(Buffer.isBuffer(val)){
|
||||||
if(length === this.size){
|
const length = this.length + val.length
|
||||||
this.resize()
|
if(length >= this.size){
|
||||||
|
this.resize()
|
||||||
|
if(length >= this.size){
|
||||||
|
throw Error('INVALID_BUFFER_STATE')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const buf = this.buf
|
||||||
|
this.buf = Buffer.alloc(this.size)
|
||||||
|
val.copy(this.buf, 0)
|
||||||
|
buf.copy(this.buf, val.length)
|
||||||
|
this.length += val.length
|
||||||
|
}else{
|
||||||
|
const length = this.length++
|
||||||
|
if(length === this.size){
|
||||||
|
this.resize()
|
||||||
|
}
|
||||||
|
const buf = this.clone()
|
||||||
|
this.buf[0] = val
|
||||||
|
buf.copy(this.buf,1, 0, length)
|
||||||
}
|
}
|
||||||
const buf = this.clone()
|
|
||||||
this.buf[0] = val
|
|
||||||
buf.copy(this.buf,1, 0, length)
|
|
||||||
}
|
}
|
||||||
append(val){
|
append(val){
|
||||||
const length = this.length++
|
const length = this.length++
|
||||||
@ -13217,11 +13296,15 @@ class ResizeableBuffer{
|
|||||||
this.buf.copy(buf,0, 0, length)
|
this.buf.copy(buf,0, 0, length)
|
||||||
this.buf = buf
|
this.buf = buf
|
||||||
}
|
}
|
||||||
toString(){
|
toString(encoding){
|
||||||
return this.buf.slice(0, this.length).toString()
|
if(encoding){
|
||||||
|
return this.buf.slice(0, this.length).toString(encoding)
|
||||||
|
}else{
|
||||||
|
return Uint8Array.prototype.slice.call(this.buf.slice(0, this.length))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
toJSON(){
|
toJSON(){
|
||||||
return this.toString()
|
return this.toString('utf8')
|
||||||
}
|
}
|
||||||
reset(){
|
reset(){
|
||||||
this.length = 0
|
this.length = 0
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.0.4",
|
"@actions/exec": "^1.0.4",
|
||||||
"@actions/github": "^4.0.0",
|
"@actions/github": "^4.0.0",
|
||||||
"csv-parse": "^4.12.0",
|
"csv-parse": "^4.14.0",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"tmp": "^0.2.1"
|
"tmp": "^0.2.1"
|
||||||
},
|
},
|
||||||
|
@ -1236,10 +1236,10 @@ cssstyle@^2.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cssom "~0.3.6"
|
cssom "~0.3.6"
|
||||||
|
|
||||||
csv-parse@*, csv-parse@^4.12.0:
|
csv-parse@*, csv-parse@^4.14.0:
|
||||||
version "4.12.0"
|
version "4.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.12.0.tgz#fd42d6291bbaadd51d3009f6cadbb3e53b4ce026"
|
resolved "https://registry.yarnpkg.com/csv-parse/-/csv-parse-4.14.0.tgz#5e1f62234a9790de49e8322df823187f8d500d26"
|
||||||
integrity sha512-wPQl3H79vWLPI8cgKFcQXl0NBgYYEqVnT1i6/So7OjMpsI540oD7p93r3w6fDSyPvwkTepG05F69/7AViX2lXg==
|
integrity sha512-Mh6JEKAD4/scRM7jbQ5FbhuHbAVp+FN27S6PMF5ILuR7ofclfR0Fj6YmutFMXVgaNk/4GTy9bkmV8SMzwsv45Q==
|
||||||
|
|
||||||
dashdash@^1.12.0:
|
dashdash@^1.12.0:
|
||||||
version "1.14.1"
|
version "1.14.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user