Skip to content

Commit

Permalink
Merge pull request #224 from RaoHai/feat/add-error-detect
Browse files Browse the repository at this point in the history
feat: add error detect
  • Loading branch information
RaoHai authored Aug 31, 2020
2 parents c524d47 + 593f94b commit 59d61ed
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 44 deletions.
2 changes: 1 addition & 1 deletion example/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ import log from '!!raw-loader!./log.txt';

# Basic Usage
<Playground>
<ReactAnsi log={log} bodyStyle={{ maxHeight: 500, overflowY: 'auto' }} />
<ReactAnsi log={log} bodyStyle={{ maxHeight: 500, overflowY: 'auto' }} autoScroll/>
</Playground>
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-ansi",
"version": "1.8.1",
"version": "2.0.0",
"description": "",
"author": "RaoHai",
"license": "MIT",
Expand Down Expand Up @@ -49,6 +49,7 @@
"cross-env": "^6.0.3",
"docz": "^2.1.1",
"docz-theme-default": "^1.2.0",
"docz-utils": "2.3.0",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.13.2",
"gatsby-plugin-less": "^3.0.17",
Expand Down
34 changes: 20 additions & 14 deletions src/component/LogContent.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import React from 'react';
import { Partical } from '../matcher';
import { RawLogger } from './RawLogger';
import { ErrorMatcher } from '../errorMatcher';

import styles from '../style/log.module.less';

export interface LogContent {
particals: Partical[];
style?: React.CSSProperties;
linkify?: boolean;
errorMatcher: ErrorMatcher;
}

export function LogContent({ particals, style, linkify }: LogContent) {
return <pre id="log" className={styles.ansi} style={style}>
{particals.map((partical, index) => {
return (
<RawLogger
key={`logger-line-${index}`}
foldable={partical.type === 'partical'}
partical={partical}
index={index}
linkify={linkify}
/>
);
})}
</pre>;
export function LogContent({ particals, style, linkify, errorMatcher }: LogContent) {
return (
<pre id="log" className={styles.ansi} style={style}>
{particals.map((partical, index) => {
return (
<RawLogger
key={`logger-line-${index}`}
foldable={partical.type === 'partical'}
partical={partical}
index={index}
linkify={linkify}
errorMatcher={errorMatcher}
/>
);
})}
</pre>
);
}
76 changes: 60 additions & 16 deletions src/component/RawLogger.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React, { useState, ReactNode } from 'react';
import React, { useState, ReactNode, useMemo, useEffect, useContext } from 'react';
import Anser, { AnserJsonEntry } from 'anser';
import { escapeCarriageReturn } from 'escape-carriage';
import styles from '../style/log.module.less';
import { Partical } from '../matcher';
import { ErrorMatcher } from '../errorMatcher';

import styles from '../style/log.module.less';
import { ErrorContext } from '../model/ErrorContext';

const LINK_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;

Expand Down Expand Up @@ -110,31 +113,26 @@ function convertBundleIntoReact(

export function RawLogger({
partical,
errorMatcher,
index = 0,
foldable = false,
useClasses = false,
linkify = false,
}: {
partical: Partical;
errorMatcher: ErrorMatcher;
index: number;
foldable?: boolean;
useClasses?: boolean;
linkify?: boolean;
}) {
const lineProps = { useClasses, linkify, errorMatcher };
const [fold, setFold] = useState(partical.fold);

const lines = (
<>
{partical.content.split('\n').map((line, index) => {
return (
<div className={styles.logLine} key={index}>
<a className={styles.lineNo} />
{ansiToJSON(line).map(convertBundleIntoReact.bind(null, useClasses, linkify))}
</div>
);
})}
</>
);
const lines = React.useMemo(() => {
return partical.content.split('\n').map((line, index) => (
<Line {...lineProps} line={line} index={index} />
));
}, [partical, styles, useClasses, linkify, errorMatcher]);

if (foldable) {
return (
Expand All @@ -153,5 +151,51 @@ export function RawLogger({
);
}

return lines;
return <>{lines}</>;
}

export function Line({
line,
useClasses,
linkify,
errorMatcher,
index,
}: {
line: string;
errorMatcher: ErrorMatcher;
useClasses: boolean;
linkify: boolean;
index: number,
}) {
const { addRefs } = useContext(ErrorContext);
const { errors, content } = useMemo(() => {
return ansiToJSON(line).reduce((prev, bundle, index) => {
const content = convertBundleIntoReact(useClasses, linkify, bundle, index);
const errors = errorMatcher.match(bundle);
return {
content: prev.content.concat([content]),
errors: prev.errors.concat(errors),
}
}, {
content: [] as any,
errors: [] as ErrorMatcher['patterns'],
});
}, [line]);

const ref = React.createRef<HTMLDivElement>();

useEffect(() => {
if (errors.length && ref.current) {
if (errors.length && addRefs) {
addRefs(errors, ref.current);
}
}
}, [ref, errors, addRefs]);

return (
<div className={`${styles.logLine} ${errors.length ? styles.error : ''}`} key={index} ref={ref}>
<a className={styles.lineNo} />
{content}
</div>
);
}
37 changes: 37 additions & 0 deletions src/errorMatcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { AnserJsonEntry } from 'anser';

export interface ErrorMatcherPattern {
desc: string;
link: string;
patterns: string[];
}

export type ErrorMatcherPatterns = ErrorMatcherPattern[];

export const defaultErrorMatchers = [
{
desc: 'MissingScript',
link: 'https://stackoverflow.com/questions/31976722/start-script-missing-error-when-running-npm-start',
patterns: [
'sh: 1: ts-node: not found'
],
}
];

export class ErrorMatcher {
patterns: Array<ErrorMatcherPattern & { regexs: RegExp[] }>;

constructor(errorMatchers: ErrorMatcherPatterns) {
this.patterns = errorMatchers.map(matcher => ({
...matcher,
regexs: matcher.patterns.map(regex => new RegExp(regex)),
}));
}

match(bundle: AnserJsonEntry) {
return this.patterns.map(pattern => {
const errors = pattern.regexs.map(regex => regex.exec(bundle.content)).filter(Boolean);
return errors.length ? pattern : null;
}).filter(Boolean) as ErrorMatcher['patterns'];
}
}
18 changes: 14 additions & 4 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ import React, { useRef, useEffect, useState } from 'react';
import { _ } from './utils/i18n';
import { Spliter, defaultMatchers } from './model/Spliter';

import styles from './style/log.module.less';
import { Matcher } from './matcher';
import { ErrorMatcher, defaultErrorMatchers, ErrorMatcherPatterns } from './errorMatcher';
import { LogContent } from './component/LogContent';
// import { Search } from './component/Search';
import { ErrorContext, errorRefs } from './model/ErrorContext';

import styles from './style/log.module.less';

const MemorizedLogContent = React.memo(LogContent);

export { Matcher };
export { Matcher, ErrorContext, errorRefs };
export interface FoldableLoggerProps {
log: string;
style?: React.CSSProperties;
bodyStyle?: React.CSSProperties;
logStyle?: React.CSSProperties;
matchers?: Matcher[];
errorMatchers?: ErrorMatcherPatterns;
autoScroll?: boolean;
showHeader?: boolean;
linkify?: boolean;
Expand All @@ -31,13 +34,15 @@ export default function FoldableLogger({
logStyle = {},
log,
matchers = defaultMatchers,
errorMatchers = defaultErrorMatchers,
autoScroll = false,
showHeader = false,
linkify = true,
}: FoldableLoggerProps) {
const [autoScrollFlag, setAutoScrollFlag] = useState(autoScroll);
const bodyRef = useRef<HTMLDivElement>(null);
const spliter = React.useMemo(() => new Spliter(matchers), [matchers]);
const errorMatcher = React.useMemo(() => new ErrorMatcher(errorMatchers), [errorMatchers]);

const foldedLogger = spliter.execute(log);

Expand Down Expand Up @@ -84,7 +89,12 @@ export default function FoldableLogger({

<div className={styles.logBody} style={bodyStyle} ref={bodyRef}>
{/* <Search defaultSearch /> */}
<MemorizedLogContent particals={foldedLogger} style={logStyle} linkify={linkify} />
<MemorizedLogContent
particals={foldedLogger}
style={logStyle}
linkify={linkify}
errorMatcher={errorMatcher}
/>
</div>
<div className={styles.logFooter} onClick={scrollBodyToTop}>
<a className={styles.backToTop}>{_('top')}</a>
Expand Down
15 changes: 15 additions & 0 deletions src/model/ErrorContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ErrorMatcherPattern } from '../errorMatcher';
import { createContext } from 'react';

const errorRefs = new Map<HTMLDivElement, ErrorMatcherPattern[]>();

export function addRefs(errors: ErrorMatcherPattern[], ref: HTMLDivElement) {
errorRefs.set(ref, errors);
}

export { errorRefs };

export const ErrorContext = createContext({
refs: errorRefs,
addRefs,
});
5 changes: 4 additions & 1 deletion src/style/log.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@

.logBody {
position: relative;
padding-bottom: 1em;
}

.logFooter {
Expand Down Expand Up @@ -82,6 +81,10 @@
}
}

.error {
color: #f97583;
}

.foldLine {
color: #ffff91;
cursor: pointer;
Expand Down

0 comments on commit 59d61ed

Please sign in to comment.