feat: using gh-star-fetch
This commit is contained in:
21
.eslintrc
21
.eslintrc
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2020,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"es6": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"prettier/@typescript-eslint",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:prettier/recommended"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
|
||||||
"prefer-rest-params": "off",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "off"
|
|
||||||
}
|
|
||||||
}
|
|
17
.eslintrc.js
Normal file
17
.eslintrc.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 2022,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['@typescript-eslint'],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
],
|
||||||
|
};
|
12
action.yml
12
action.yml
@@ -12,8 +12,20 @@ inputs:
|
|||||||
required: true
|
required: true
|
||||||
github-name:
|
github-name:
|
||||||
description: 'Name shown in the commit'
|
description: 'Name shown in the commit'
|
||||||
|
default: 'GitHub Actions'
|
||||||
|
required: false
|
||||||
github-email:
|
github-email:
|
||||||
description: 'Email shown in the commit'
|
description: 'Email shown in the commit'
|
||||||
|
default: 'actions@users.noreply.github.com'
|
||||||
|
required: false
|
||||||
|
template-path:
|
||||||
|
required: false
|
||||||
|
description: 'EJS template path relative to project root directory'
|
||||||
|
default: 'TEMPLATE.ejs'
|
||||||
|
output-filename:
|
||||||
|
description: 'The output file name, default to README.md'
|
||||||
|
required: false
|
||||||
|
default: 'README.md'
|
||||||
runs:
|
runs:
|
||||||
using: 'node12'
|
using: 'node12'
|
||||||
main: 'index.js'
|
main: 'index.js'
|
||||||
|
711
package-lock.json
generated
711
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -40,7 +40,7 @@
|
|||||||
"@types/node": "^17.0.31",
|
"@types/node": "^17.0.31",
|
||||||
"@types/sinon": "^10.0.11",
|
"@types/sinon": "^10.0.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.23.0",
|
"@typescript-eslint/eslint-plugin": "^5.23.0",
|
||||||
"@typescript-eslint/parser": "^4.33.0",
|
"@typescript-eslint/parser": "^5.23.0",
|
||||||
"@vercel/ncc": "^0.33.4",
|
"@vercel/ncc": "^0.33.4",
|
||||||
"ava": "^4.2.0",
|
"ava": "^4.2.0",
|
||||||
"eslint": "^8.15.0",
|
"eslint": "^8.15.0",
|
||||||
|
11
src/api.ts
11
src/api.ts
@@ -1,11 +0,0 @@
|
|||||||
import got from 'got';
|
|
||||||
import * as core from '@actions/core';
|
|
||||||
|
|
||||||
const GITHUB_TOKEN = core.getInput('api-token', { required: true });
|
|
||||||
|
|
||||||
export default got.extend({
|
|
||||||
headers: {
|
|
||||||
Authorization: `token ${GITHUB_TOKEN}`,
|
|
||||||
},
|
|
||||||
responseType: 'json',
|
|
||||||
});
|
|
10
src/git.ts
10
src/git.ts
@@ -34,8 +34,7 @@ class Git {
|
|||||||
return isShallow.trim().replace('\n', '') === 'true';
|
return isShallow.trim().replace('\n', '') === 'true';
|
||||||
};
|
};
|
||||||
|
|
||||||
exec = (command: string): Promise<string> => {
|
async exec(command: string): Promise<string> {
|
||||||
return new Promise(async (resolve, reject) => {
|
|
||||||
let execOutput = '';
|
let execOutput = '';
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
@@ -49,13 +48,12 @@ class Git {
|
|||||||
const exitCode = await exec.exec(`git ${command}`, undefined, options);
|
const exitCode = await exec.exec(`git ${command}`, undefined, options);
|
||||||
|
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
return resolve(execOutput);
|
return execOutput;
|
||||||
} else {
|
} else {
|
||||||
core.error(`Command "git ${command}" exited with code ${exitCode}.`);
|
core.error(`Command "git ${command}" exited with code ${exitCode}.`);
|
||||||
return reject(`Command "git ${command}" exited with code ${exitCode}.`);
|
throw new Error(`Command "git ${command}" exited with code ${exitCode}.`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
config = (prop: string, value: string) =>
|
config = (prop: string, value: string) =>
|
||||||
this.exec(`config ${prop} "${value}"`);
|
this.exec(`config ${prop} "${value}"`);
|
||||||
|
@@ -3,10 +3,6 @@ import ejs from 'ejs';
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import remark from 'remark';
|
import remark from 'remark';
|
||||||
import toc from 'remark-toc';
|
import toc from 'remark-toc';
|
||||||
|
|
||||||
import MD_TEMPLATE from './template';
|
|
||||||
import GithubApi from './api';
|
|
||||||
import link from './link';
|
|
||||||
import git from './git';
|
import git from './git';
|
||||||
|
|
||||||
import type { PaginationLink, ApiGetStarResponse, Stars, Star } from './types';
|
import type { PaginationLink, ApiGetStarResponse, Stars, Star } from './types';
|
||||||
@@ -16,13 +12,9 @@ export const API_STARRED_URL = `${process.env.GITHUB_API_URL}/users/${REPO_USERN
|
|||||||
|
|
||||||
const fsp = fs.promises;
|
const fsp = fs.promises;
|
||||||
|
|
||||||
export function wait(time = 200): Promise<void> {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, time));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function renderer(
|
export async function renderer(
|
||||||
data: { [key: string]: any },
|
data: { [key: string]: any },
|
||||||
templateString = MD_TEMPLATE
|
templateString: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
return ejs.render(templateString, data);
|
return ejs.render(templateString, data);
|
||||||
@@ -32,74 +24,6 @@ export async function renderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNextPage(links: PaginationLink[]): string | null {
|
|
||||||
const next = links.find((l) => l.rel === 'next');
|
|
||||||
const last = links.find((l) => l.rel === 'last');
|
|
||||||
if (!next || !last) return null;
|
|
||||||
const matchNext = next.uri.match(/page=([0-9]*)/);
|
|
||||||
const matchLast = last.uri.match(/page=([0-9]*)/);
|
|
||||||
if (!matchNext || !matchLast) return null;
|
|
||||||
if (matchNext[1] === matchLast[1]) return null;
|
|
||||||
return matchNext[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function* paginateStars(url: string): AsyncGenerator<Stars> {
|
|
||||||
let nextPage: string | null = '1';
|
|
||||||
while (nextPage) {
|
|
||||||
try {
|
|
||||||
const { headers, body } = await GithubApi.get(url, {
|
|
||||||
searchParams: {
|
|
||||||
page: nextPage,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
yield (body as unknown) as Stars;
|
|
||||||
nextPage = getNextPage(link.parse(headers.link).refs);
|
|
||||||
await wait(1000); // avoid limits
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function apiGetStar(
|
|
||||||
url: string = API_STARRED_URL
|
|
||||||
): Promise<ApiGetStarResponse> {
|
|
||||||
const data: Partial<Star>[] = [];
|
|
||||||
for await (const stars of paginateStars(url)) {
|
|
||||||
for (const star of stars) {
|
|
||||||
data.push({
|
|
||||||
id: star.id,
|
|
||||||
node_id: star.node_id,
|
|
||||||
name: star.name,
|
|
||||||
full_name: star.full_name,
|
|
||||||
owner: {
|
|
||||||
login: star?.owner?.login,
|
|
||||||
id: star?.owner?.id,
|
|
||||||
avatar_url: star?.owner?.avatar_url,
|
|
||||||
url: star?.owner?.url,
|
|
||||||
html_url: star?.owner?.html_url,
|
|
||||||
},
|
|
||||||
html_url: star.html_url,
|
|
||||||
description: star.description,
|
|
||||||
url: star.url,
|
|
||||||
languages_url: star.languages_url,
|
|
||||||
created_at: star.created_at,
|
|
||||||
updated_at: star.updated_at,
|
|
||||||
git_url: star.git_url,
|
|
||||||
ssh_url: star.ssh_url,
|
|
||||||
clone_url: star.clone_url,
|
|
||||||
homepage: star.homepage,
|
|
||||||
stargazers_count: star.stargazers_count,
|
|
||||||
watchers_count: star.watchers_count,
|
|
||||||
language: star.language,
|
|
||||||
topics: star.topics,
|
|
||||||
} as Partial<Star>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (data as unknown) as Stars;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateMd(data: string): Promise<string> {
|
export function generateMd(data: string): Promise<string> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
remark()
|
remark()
|
||||||
@@ -115,8 +39,7 @@ export function generateMd(data: string): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MARKDOWN_FILENAME: string =
|
export const MARKDOWN_FILENAME: string = core.getInput('output-filename');
|
||||||
core.getInput('output-filename') || 'README.md';
|
|
||||||
|
|
||||||
type File = {
|
type File = {
|
||||||
filename: string;
|
filename: string;
|
||||||
|
30
src/index.ts
30
src/index.ts
@@ -1,5 +1,6 @@
|
|||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import { readdir, readFile } from 'fs/promises';
|
import { readdir, readFile } from 'fs/promises';
|
||||||
|
import ghStarFetch from 'gh-star-fetch';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
renderer,
|
renderer,
|
||||||
@@ -7,34 +8,25 @@ import {
|
|||||||
generateMd,
|
generateMd,
|
||||||
pushNewFiles,
|
pushNewFiles,
|
||||||
MARKDOWN_FILENAME,
|
MARKDOWN_FILENAME,
|
||||||
apiGetStar,
|
|
||||||
} from './helpers';
|
} from './helpers';
|
||||||
|
import MD_TEMPLATE from './template';
|
||||||
|
|
||||||
import type { SortedLanguageList, Stars, Star } from './types';
|
export async function main() {
|
||||||
|
const sortedByLanguages = await ghStarFetch({
|
||||||
|
accessToken: core.getInput('api-token', { required: true }),
|
||||||
|
compactByLanguage: true,
|
||||||
|
});
|
||||||
|
|
||||||
export async function main(): Promise<any> {
|
// set default template
|
||||||
const results: Stars = await apiGetStar();
|
let template = MD_TEMPLATE;
|
||||||
|
|
||||||
const sortedByLanguages = results.reduce(
|
|
||||||
(acc: SortedLanguageList, val: Star) => {
|
|
||||||
const language = val.language || 'generic';
|
|
||||||
if (!acc[language]) {
|
|
||||||
acc[language] = [val];
|
|
||||||
} else {
|
|
||||||
acc[language].push(val);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
// get template if found in the repo
|
// get template if found in the repo
|
||||||
let template;
|
const customTemplatePath = core.getInput('template-path');
|
||||||
|
core.info(`check if customTemplatePath: ${customTemplatePath} exists`);
|
||||||
try {
|
try {
|
||||||
const dir = await readdir('./');
|
const dir = await readdir('./');
|
||||||
core.info(dir.join('\n'));
|
core.info(dir.join('\n'));
|
||||||
template = await readFile('TEMPLATE.ejs', 'utf8');
|
template = await readFile('TEMPLATE.ejs', 'utf8');
|
||||||
core.info(template);
|
|
||||||
} catch {
|
} catch {
|
||||||
core.warning("Couldn't find template file, using default");
|
core.warning("Couldn't find template file, using default");
|
||||||
}
|
}
|
||||||
|
384
src/link.js
384
src/link.js
@@ -1,384 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2016 Jonas Hermsmeier
|
|
||||||
* https://github.com/jhermsmeier/node-http-link-header
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* istanbul ignore file */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var COMPATIBLE_ENCODING_PATTERN = /^utf-?8|ascii|utf-?16-?le|ucs-?2|base-?64|latin-?1$/i;
|
|
||||||
var WS_TRIM_PATTERN = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
|
|
||||||
var WS_CHAR_PATTERN = /\s|\uFEFF|\xA0/;
|
|
||||||
var WS_FOLD_PATTERN = /\r?\n[\x20\x09]+/g;
|
|
||||||
var DELIMITER_PATTERN = /[;,"]/;
|
|
||||||
var WS_DELIMITER_PATTERN = /[;,"]|\s/;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Token character pattern
|
|
||||||
* @type {RegExp}
|
|
||||||
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
||||||
*/
|
|
||||||
var TOKEN_PATTERN = /^[!#$%&'*+\-\.^_`|~\da-zA-Z]+$/;
|
|
||||||
|
|
||||||
var STATE = {
|
|
||||||
IDLE: 1 << 0,
|
|
||||||
URI: 1 << 1,
|
|
||||||
ATTR: 1 << 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
function trim(value) {
|
|
||||||
return value.replace(WS_TRIM_PATTERN, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasWhitespace(value) {
|
|
||||||
return WS_CHAR_PATTERN.test(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function skipWhitespace(value, offset) {
|
|
||||||
while (hasWhitespace(value[offset])) {
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
function needsQuotes(value) {
|
|
||||||
return WS_DELIMITER_PATTERN.test(value) || !TOKEN_PATTERN.test(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Link {
|
|
||||||
/**
|
|
||||||
* Link
|
|
||||||
* @constructor
|
|
||||||
* @param {String} [value]
|
|
||||||
* @returns {Link}
|
|
||||||
*/
|
|
||||||
constructor(value) {
|
|
||||||
/** @type {Array} URI references */
|
|
||||||
this.refs = [];
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
this.parse(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get refs with given relation type
|
|
||||||
* @param {String} value
|
|
||||||
* @returns {Array<Object>}
|
|
||||||
*/
|
|
||||||
rel(value) {
|
|
||||||
var links = [];
|
|
||||||
var type = value.toLowerCase();
|
|
||||||
|
|
||||||
for (var i = 0; i < this.refs.length; i++) {
|
|
||||||
if (this.refs[i].rel.toLowerCase() === type) {
|
|
||||||
links.push(this.refs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get refs where given attribute has a given value
|
|
||||||
* @param {String} attr
|
|
||||||
* @param {String} value
|
|
||||||
* @returns {Array<Object>}
|
|
||||||
*/
|
|
||||||
get(attr, value) {
|
|
||||||
attr = attr.toLowerCase();
|
|
||||||
|
|
||||||
var links = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < this.refs.length; i++) {
|
|
||||||
if (this.refs[i][attr] === value) {
|
|
||||||
links.push(this.refs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return links;
|
|
||||||
}
|
|
||||||
|
|
||||||
set(link) {
|
|
||||||
this.refs.push(link);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
has(attr, value) {
|
|
||||||
attr = attr.toLowerCase();
|
|
||||||
|
|
||||||
for (var i = 0; i < this.refs.length; i++) {
|
|
||||||
if (this.refs[i][attr] === value) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
parse(value, offset) {
|
|
||||||
offset = offset || 0;
|
|
||||||
value = offset ? value.slice(offset) : value;
|
|
||||||
|
|
||||||
// Trim & unfold folded lines
|
|
||||||
value = trim(value).replace(WS_FOLD_PATTERN, '');
|
|
||||||
|
|
||||||
var state = STATE.IDLE;
|
|
||||||
var length = value.length;
|
|
||||||
var offset = 0;
|
|
||||||
var ref = null;
|
|
||||||
|
|
||||||
while (offset < length) {
|
|
||||||
if (state === STATE.IDLE) {
|
|
||||||
if (hasWhitespace(value[offset])) {
|
|
||||||
offset++;
|
|
||||||
continue;
|
|
||||||
} else if (value[offset] === '<') {
|
|
||||||
if (ref != null) {
|
|
||||||
ref.rel != null
|
|
||||||
? this.refs.push(...Link.expandRelations(ref))
|
|
||||||
: this.refs.push(ref);
|
|
||||||
}
|
|
||||||
var end = value.indexOf('>', offset);
|
|
||||||
if (end === -1)
|
|
||||||
throw new Error(
|
|
||||||
'Expected end of URI delimiter at offset ' + offset
|
|
||||||
);
|
|
||||||
ref = { uri: value.slice(offset + 1, end) };
|
|
||||||
// this.refs.push( ref )
|
|
||||||
offset = end;
|
|
||||||
state = STATE.URI;
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
'Unexpected character "' + value[offset] + '" at offset ' + offset
|
|
||||||
);
|
|
||||||
}
|
|
||||||
offset++;
|
|
||||||
} else if (state === STATE.URI) {
|
|
||||||
if (hasWhitespace(value[offset])) {
|
|
||||||
offset++;
|
|
||||||
continue;
|
|
||||||
} else if (value[offset] === ';') {
|
|
||||||
state = STATE.ATTR;
|
|
||||||
offset++;
|
|
||||||
} else if (value[offset] === ',') {
|
|
||||||
state = STATE.IDLE;
|
|
||||||
offset++;
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
'Unexpected character "' + value[offset] + '" at offset ' + offset
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (state === STATE.ATTR) {
|
|
||||||
if (value[offset] === ';' || hasWhitespace(value[offset])) {
|
|
||||||
offset++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var end = value.indexOf('=', offset);
|
|
||||||
if (end === -1)
|
|
||||||
throw new Error('Expected attribute delimiter at offset ' + offset);
|
|
||||||
var attr = trim(value.slice(offset, end)).toLowerCase();
|
|
||||||
var attrValue = '';
|
|
||||||
offset = end + 1;
|
|
||||||
offset = skipWhitespace(value, offset);
|
|
||||||
if (value[offset] === '"') {
|
|
||||||
offset++;
|
|
||||||
while (offset < length) {
|
|
||||||
if (value[offset] === '"') {
|
|
||||||
offset++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (value[offset] === '\\') {
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
attrValue += value[offset];
|
|
||||||
offset++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var end = offset + 1;
|
|
||||||
while (!DELIMITER_PATTERN.test(value[end]) && end < length) {
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
attrValue = value.slice(offset, end);
|
|
||||||
offset = end;
|
|
||||||
}
|
|
||||||
if (ref[attr] && Link.isSingleOccurenceAttr(attr)) {
|
|
||||||
// Ignore multiples of attributes which may only appear once
|
|
||||||
} else if (attr[attr.length - 1] === '*') {
|
|
||||||
ref[attr] = Link.parseExtendedValue(attrValue);
|
|
||||||
} else {
|
|
||||||
attrValue = attr === 'type' ? attrValue.toLowerCase() : attrValue;
|
|
||||||
if (ref[attr] != null) {
|
|
||||||
if (Array.isArray(ref[attr])) {
|
|
||||||
ref[attr].push(attrValue);
|
|
||||||
} else {
|
|
||||||
ref[attr] = [ref[attr], attrValue];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ref[attr] = attrValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch (value[offset]) {
|
|
||||||
case ',':
|
|
||||||
state = STATE.IDLE;
|
|
||||||
break;
|
|
||||||
case ';':
|
|
||||||
state = STATE.ATTR;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
offset++;
|
|
||||||
} else {
|
|
||||||
throw new Error('Unknown parser state "' + state + '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ref != null) {
|
|
||||||
ref.rel != null
|
|
||||||
? this.refs.push(...Link.expandRelations(ref))
|
|
||||||
: this.refs.push(ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
ref = null;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
var refs = [];
|
|
||||||
var link = '';
|
|
||||||
var ref = null;
|
|
||||||
|
|
||||||
for (var i = 0; i < this.refs.length; i++) {
|
|
||||||
ref = this.refs[i];
|
|
||||||
link = Object.keys(this.refs[i]).reduce(function (link, attr) {
|
|
||||||
if (attr === 'uri') return link;
|
|
||||||
return link + '; ' + Link.formatAttribute(attr, ref[attr]);
|
|
||||||
}, '<' + ref.uri + '>');
|
|
||||||
refs.push(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
return refs.join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether an encoding can be
|
|
||||||
* natively handled with a `Buffer`
|
|
||||||
* @param {String} value
|
|
||||||
* @returns {Boolean}
|
|
||||||
*/
|
|
||||||
Link.isCompatibleEncoding = function (value) {
|
|
||||||
return COMPATIBLE_ENCODING_PATTERN.test(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
Link.parse = function (value, offset) {
|
|
||||||
return new Link().parse(value, offset);
|
|
||||||
};
|
|
||||||
|
|
||||||
Link.isSingleOccurenceAttr = function (attr) {
|
|
||||||
return (
|
|
||||||
attr === 'rel' ||
|
|
||||||
attr === 'type' ||
|
|
||||||
attr === 'media' ||
|
|
||||||
attr === 'title' ||
|
|
||||||
attr === 'title*'
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Link.isTokenAttr = function (attr) {
|
|
||||||
return attr === 'rel' || attr === 'type' || attr === 'anchor';
|
|
||||||
};
|
|
||||||
|
|
||||||
Link.escapeQuotes = function (value) {
|
|
||||||
return value.replace(/"/g, '\\"');
|
|
||||||
};
|
|
||||||
|
|
||||||
Link.expandRelations = function (ref) {
|
|
||||||
var rels = ref.rel.split(' ');
|
|
||||||
return rels.map(function (rel) {
|
|
||||||
var value = Object.assign({}, ref);
|
|
||||||
value.rel = rel;
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses an extended value and attempts to decode it
|
|
||||||
* @internal
|
|
||||||
* @param {String} value
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
Link.parseExtendedValue = function (value) {
|
|
||||||
var parts = /([^']+)?(?:'([^']+)')?(.+)/.exec(value);
|
|
||||||
return {
|
|
||||||
language: parts[2].toLowerCase(),
|
|
||||||
encoding: Link.isCompatibleEncoding(parts[1])
|
|
||||||
? null
|
|
||||||
: parts[1].toLowerCase(),
|
|
||||||
value: Link.isCompatibleEncoding(parts[1])
|
|
||||||
? decodeURIComponent(parts[3])
|
|
||||||
: parts[3],
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a given extended attribute and it's value
|
|
||||||
* @param {String} attr
|
|
||||||
* @param {Object} data
|
|
||||||
* @return {String}
|
|
||||||
*/
|
|
||||||
Link.formatExtendedAttribute = function (attr, data) {
|
|
||||||
var encoding = (data.encoding || 'utf-8').toUpperCase();
|
|
||||||
var language = data.language || 'en';
|
|
||||||
|
|
||||||
var encodedValue = '';
|
|
||||||
|
|
||||||
if (Buffer.isBuffer(data.value) && Link.isCompatibleEncoding(encoding)) {
|
|
||||||
encodedValue = data.value.toString(encoding);
|
|
||||||
} else if (Buffer.isBuffer(data.value)) {
|
|
||||||
encodedValue = data.value.toString('hex').replace(/[0-9a-f]{2}/gi, '%$1');
|
|
||||||
} else {
|
|
||||||
encodedValue = encodeURIComponent(data.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return attr + '=' + encoding + "'" + language + "'" + encodedValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a given attribute and it's value
|
|
||||||
* @param {String} attr
|
|
||||||
* @param {String|Object} value
|
|
||||||
* @return {String}
|
|
||||||
*/
|
|
||||||
Link.formatAttribute = function (attr, value) {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value
|
|
||||||
.map((item) => {
|
|
||||||
return Link.formatAttribute(attr, item);
|
|
||||||
})
|
|
||||||
.join('; ');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attr[attr.length - 1] === '*' || typeof value !== 'string') {
|
|
||||||
return Link.formatExtendedAttribute(attr, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Link.isTokenAttr(attr)) {
|
|
||||||
value = needsQuotes(value)
|
|
||||||
? '"' + Link.escapeQuotes(value) + '"'
|
|
||||||
: Link.escapeQuotes(value);
|
|
||||||
} else if (needsQuotes(value)) {
|
|
||||||
value = encodeURIComponent(value);
|
|
||||||
// We don't need to escape <SP> <,> <;> within quotes
|
|
||||||
value = value
|
|
||||||
.replace(/%20/g, ' ')
|
|
||||||
.replace(/%2C/g, ',')
|
|
||||||
.replace(/%3B/g, ';');
|
|
||||||
|
|
||||||
value = '"' + value + '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
return attr + '=' + value;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Link;
|
|
Reference in New Issue
Block a user