Browse Source

Regex builder. Searcher builder. General search and displace improvements

master
Orzu Ionut 3 years ago
parent
commit
bcb09c4f9b
  1. 36
      app/Http/Controllers/RegexController.php
  2. 7
      app/Http/Controllers/SearchAndDisplaceController.php
  3. 80
      app/Http/Controllers/SearcherController.php
  4. 28
      app/SearchDisplace/Regex/RegexFactory.php
  5. 27
      app/SearchDisplace/SearchAndDisplace.php
  6. 4
      app/SearchDisplace/SearchAndDisplaceFromFiles.php
  7. 202
      app/SearchDisplace/Searchers/Searcher.php
  8. 36
      app/SearchDisplace/Searchers/SearcherCreator.php
  9. 18
      app/SearchDisplace/Searchers/SearcherFactory.php
  10. 50
      app/SearchDisplace/Searchers/SearchersStorage.php
  11. 51585
      package-lock.json
  12. 3
      package.json
  13. 890
      public/css/app.css
  14. 17342
      public/js/app.js
  15. 23
      resources/js/app.ts
  16. 4
      resources/js/bootstrap.ts
  17. 2
      resources/js/components/ProcessFile.vue
  18. 69
      resources/js/components/Regex/Create.vue
  19. 46
      resources/js/components/Regex/Flags.vue
  20. 45
      resources/js/components/Regex/PatternBox.vue
  21. 225
      resources/js/components/Regex/SideBar.vue
  22. 39
      resources/js/components/Regex/TextBox.vue
  23. 62
      resources/js/components/Searchers/AddBox.vue
  24. 65
      resources/js/components/Searchers/Create.vue
  25. 65
      resources/js/components/Searchers/Index.vue
  26. 50
      resources/js/components/Searchers/Show.vue
  27. 54
      resources/sass/_layout.sass
  28. 60
      resources/sass/_layout.scss
  29. 7
      resources/sass/_variables.sass
  30. 7
      resources/sass/_variables.scss
  31. 79
      resources/sass/app.sass
  32. 48
      resources/sass/app.scss
  33. 1
      resources/sass/components/_index.sass
  34. 1
      resources/sass/components/regex/_index.sass
  35. 9
      resources/sass/components/regex/create/_flags.sass
  36. 43
      resources/sass/components/regex/create/_index.sass
  37. 26
      resources/sass/components/regex/create/_pattern-box.sass
  38. 63
      resources/sass/components/regex/create/_references.sass
  39. 63
      resources/sass/components/regex/create/_text-box.sass
  40. 10
      resources/views/app.blade.php
  41. 12
      resources/views/pages/home.blade.php
  42. 5
      resources/views/pages/regex/create.blade.php
  43. 5
      resources/views/pages/searchers/create.blade.php
  44. 7
      resources/views/pages/searchers/show.blade.php
  45. 13
      routes/web.php
  46. 2
      webpack.mix.js
  47. 10190
      yarn.lock

36
app/Http/Controllers/RegexController.php

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers;
use App\SearchDisplace\Regex\RegexFactory;
class RegexController extends Controller
{
public function create()
{
return view('pages.regex.create');
}
public function store()
{
request()->validate([
'name' => 'required',
'expression' => 'required',
]);
try {
$factory = new RegexFactory(request()->get('name'), request()->get('expression'));
$factory->create();
return response()->json([
'status' => 'success',
], 200);
} catch (\Exception $exception) {
return response()->json([
'status' => 'fail',
'message' => $exception->getMessage(),
], 400);
}
}
}

7
app/Http/Controllers/SearchAndDisplaceController.php

@ -37,14 +37,11 @@ class SearchAndDisplaceController extends Controller
]);
try {
$resultedDocumentContent = $searchAndDisplace->execute();
return response()->json([
'content' => $resultedDocumentContent,
], 200);
return response()->json($searchAndDisplace->execute(), 200);
} catch (\Exception $exception) {
return response()->json([
'message' => $exception->getMessage(),
'trace' => $exception->getTrace(),
], 400);
}
}

80
app/Http/Controllers/SearcherController.php

@ -0,0 +1,80 @@
<?php
namespace App\Http\Controllers;
use App\SearchDisplace\Searchers\SearcherFactory;
use App\SearchDisplace\Searchers\SearchersStorage;
class SearcherController extends Controller
{
public function index()
{
// @TODO Add filters
if (request()->has('q')) {
$searchers = [];
} else {
$searchers = (new SearchersStorage())->all();
}
if (request()->wantsJson()) {
return response()->json([
'searchers' => $searchers,
], 200);
}
return view('pages.searchers.index', [
'searchers' => $searchers,
]);
}
public function create()
{
return view('pages.searchers.create');
}
public function store()
{
request()->validate([
'name' => 'required',
'rows' => 'required|array',
'rows.*' => 'array',
]);
try {
$factory = new SearcherFactory(request()->get('name'), request()->get('rows'));
$factory->create();
return response()->json([
'status' => 'success',
], 200);
} catch (\Exception $exception) {
return response()->json([
'status' => 'fail',
'message' => $exception->getMessage(),
], 400);
}
}
public function show($id)
{
$searchersStorage = new SearchersStorage();
try {
if ( ! $searchersStorage->has($id)) {
abort(404);
}
return view('pages.searchers.show', [
'searcher' => $searchersStorage->get($id),
]);
} catch (\Exception $exception) {
abort(400);
}
}
public function update($id)
{
}
}

28
app/SearchDisplace/Regex/RegexFactory.php

@ -0,0 +1,28 @@
<?php
namespace App\SearchDisplace\Regex;
use App\SearchDisplace\Searchers\SearcherCreator;
class RegexFactory extends SearcherCreator
{
protected $expression;
public function __construct($name, $expression)
{
parent::__construct($name, '');
$this->expression = $expression;
}
public function create()
{
$this->rows = [
[
[ 'expression' => $this->expression, ]
],
];
$this->store();
}
}

27
app/SearchDisplace/SearchAndDisplace.php

@ -36,6 +36,7 @@ class SearchAndDisplace
protected function displace($searchResult)
{
$replacementIndexes = [];
$replacements = [];
foreach ($this->info['searchers'] as $searcher) {
@ -45,20 +46,34 @@ class SearchAndDisplace
$updatedDocumentContent = '';
$currentIndex = 0;
foreach ($searchResult as $item) {
$partialContent = substr($this->documentContent, $currentIndex, $item['start'] - $currentIndex);
foreach ($searchResult as $searcher => $searcherResults) {
$replacementIndexes[$searcher] = [];
$updatedDocumentContent = $updatedDocumentContent . $partialContent;
foreach ($searcherResults as $searcherResult) {
$partialContent = substr($this->documentContent, $currentIndex, $searcherResult['start'] - $currentIndex);
$updatedDocumentContent = $updatedDocumentContent . $replacements[$item['dim']];
$updatedDocumentContent = $updatedDocumentContent . $partialContent;
$currentIndex = $item['end'];
$start = strlen($updatedDocumentContent);
$updatedDocumentContent = $updatedDocumentContent . $replacements[$searcher];
$replacementIndexes[$searcher][] = [
'start' => $start,
'end' => strlen($updatedDocumentContent) - 1,
];
$currentIndex = $searcherResult['end'] + 1;
}
}
if ($currentIndex < strlen($this->documentContent) - 1) {
$updatedDocumentContent = $updatedDocumentContent . substr($this->documentContent, $currentIndex);
}
return $updatedDocumentContent;
return [
'content' => $updatedDocumentContent,
'indexes' => $replacementIndexes,
];
}
}

4
app/SearchDisplace/SearchAndDisplaceFromFiles.php

@ -41,9 +41,9 @@ class SearchAndDisplaceFromFiles
$resultedDocumentPath = $pathDetails['dirname'] . '/' . $pathDetails['filename'] . '-displaced.md';
try {
$resultedDocumentContent = $searchAndDisplace->execute();
$result = $searchAndDisplace->execute();
file_put_contents($resultedDocumentPath, $resultedDocumentContent);
file_put_contents($resultedDocumentPath, $result['content']);
} catch (\Exception $exception) {
\Illuminate\Support\Facades\Log::info('exception: ' . $exception->getMessage());

202
app/SearchDisplace/Searchers/Searcher.php

@ -6,6 +6,8 @@ class Searcher
{
protected $searchers;
protected $content;
protected $ducklingSearchers;
protected $searchersStorage;
public function __construct($searchers, $content)
{
@ -13,28 +15,210 @@ class Searcher
$this->content = $content;
ksort($this->searchers);
$this->ducklingSearchers = (new Mapper())->getSearchers();
$this->searchersStorage = new SearchersStorage();
}
/**
* Handle searchers.
*
* @return mixed
* @throws \Exception
*/
public function execute()
{
$mapper = new Mapper();
$results = [];
$content = $this->content;
// Apply searchers in a half serial, half parallel manner, so apply each searcher on the same initial content
// but with the modifications made by the previous searchers, this way the 'order' matters.
foreach ($this->searchers as $searcher) {
$results[$searcher['key']] = in_array($searcher['key'], $this->ducklingSearchers)
? $this->applyDucklingSearcher($content, $searcher['key'])
: $this->applyCustomSearcher($content, $searcher);
}
return $this->processResults($results);
}
protected function applyDucklingSearcher($content, $dimension)
{
$duckling = new Duckling();
$result = $duckling->execute($content, [$dimension]);
return array_map(function ($item) {
return [
'start' => $item['start'],
'end' => $item['end'] - 1,
'content' => $item['body'],
];
}, $result);
}
/**
* @param $content
* @param $searcher
* @return mixed
* @throws \Exception
*/
protected function applyCustomSearcher($content, $searcher)
{
if (!$this->searchersStorage->has($searcher['key'])) {
throw new \Exception('Invalid searcher: ' . $searcher['key']);
}
$validSearchers = $mapper->getSearchers();
$searcherData = $this->searchersStorage->get($searcher['key']);
// The object should contain the searcher name, other info, like the user input.
return $this->handleSearcher($searcherData, $content);
}
$dimensions = [];
/**
* @param $searcher
* @param $content
* @return mixed
* @throws \Exception
*/
protected function handleSearcher($searcher, $content, $mustMatchStartAndEnd = false)
{
if (array_key_exists('rows', $searcher)) {
return $this->handleParallelSearchers($searcher['rows'], $content, $mustMatchStartAndEnd);
}
foreach ($this->searchers as $orderNumber => $searcher) {
if ( ! in_array($searcher['key'], $validSearchers)) {
if (array_key_exists('key', $searcher)) {
if (in_array($searcher['key'], $this->ducklingSearchers)) {
return $this->applyDucklingSearcher($content, $searcher['key']);
} else {
throw new \Exception('Invalid searcher: ' . $searcher['key']);
}
}
$dimensions[] = $searcher['key'];
if (array_key_exists('expression', $searcher)) {
return $this->handleExpression($searcher['expression'], $content, $mustMatchStartAndEnd);
}
$duckling = new Duckling();
throw new \Exception('Invalid searcher.');
}
/**
* @param $searchers
* @param $content
* @param bool $mustMatchStartAndEnd
* @return array|array[]
* @throws \Exception
*/
protected function handleParallelSearchers($searchers, $content, $mustMatchStartAndEnd = false)
{
// Parallel searchers. Apply searcher on the previous searcher's result.
$serialSearchersResults = [
[
'start' => 0,
'end' => strlen($content) - 1,
'content' => $content,
]
];
foreach ($searchers as $row) {
$newSerialSearchersResults = [];
foreach ($serialSearchersResults as $serialSearcherItem) {
$newSerialSearcherResult = $this->handleSerialSearchers(
$row,
$serialSearcherItem['content'],
$mustMatchStartAndEnd
);
foreach ($newSerialSearcherResult as $newSerialSearcherItem) {
$start = $serialSearcherItem['start'] + $newSerialSearcherItem['start'];
$newSerialSearchersResults[] = [
'start' => $start,
'end' => $start + strlen($newSerialSearcherItem['content']) - 1,
'content' => $newSerialSearcherItem['content'],
];
}
}
$serialSearchersResults = $newSerialSearchersResults;
$mustMatchStartAndEnd = true;
}
return $serialSearchersResults;
}
/**
* @param $serialSearchers
* @param $content
* @return mixed
* @throws \Exception
*/
protected function handleSerialSearchers($serialSearchers, $content, $mustMatchStartAndEnd)
{
$results = [];
foreach ($serialSearchers as $searcher) {
$searcherResult = $this->handleSearcher($searcher, $content, $mustMatchStartAndEnd);
$results = array_merge($results, $searcherResult);
}
return $results;
}
protected function handleExpression($expression, $content, $mustMatchStartAndEnd)
{
$pattern = $mustMatchStartAndEnd
? "/^$expression$/"
: "/$expression/";
$hasMatches = preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE);
if ( ! $hasMatches) {
return [];
}
return array_map(function ($item) {
return [
'start' => $item[1],
'end' => $item[1] + strlen($item[0]) - 1,
'content' => $item[0],
];
}, $matches[0]);
}
protected function processResults($results)
{
$intervals = [];
$processedResults = [];
foreach ($results as $searcher => $searcherResults) {
$processedResults[$searcher] = [];
foreach ($searcherResults as $item) {
$intervalIsOk = true;
foreach ($intervals as $start => $end) {
if (
$item['start'] >= $start && $item['start'] <= $end ||
$item['end'] >= $start && $item['end'] <= $end ||
$item['start'] <= $start && $item['end'] >= $end
) {
$intervalIsOk = false;
break;
}
}
if ($intervalIsOk) {
$intervals[$item['start']] = $item['end'];
$processedResults[$searcher][] = $item;
}
}
}
return $duckling->execute($this->content, $dimensions);
return $processedResults;
}
}

36
app/SearchDisplace/Searchers/SearcherCreator.php

@ -0,0 +1,36 @@
<?php
namespace App\SearchDisplace\Searchers;
use Illuminate\Support\Facades\Storage;
abstract class SearcherCreator
{
protected $name;
protected $id;
protected $description;
protected $rows;
protected $storage;
public function __construct($name, $description)
{
$this->name = $name;
$this->description = $description;
$this->id = md5(uniqid(rand(), true));
$this->storage = Storage::disk('local');
}
abstract public function create();
protected function store()
{
$contents = [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'rows' => $this->rows,
];
$this->storage->put("searchers/{$this->id}_{$this->name}.json", json_encode($contents));
}
}

18
app/SearchDisplace/Searchers/SearcherFactory.php

@ -0,0 +1,18 @@
<?php
namespace App\SearchDisplace\Searchers;
class SearcherFactory extends SearcherCreator
{
public function __construct($name, $rows)
{
parent::__construct($name, '');
$this->rows = $rows;
}
public function create()
{
$this->store();
}
}

50
app/SearchDisplace/Searchers/SearchersStorage.php

@ -0,0 +1,50 @@
<?php
namespace App\SearchDisplace\Searchers;
use Illuminate\Support\Facades\Storage;
class SearchersStorage
{
protected $storage;
protected $searchers = [];
public function __construct()
{
$this->storage = Storage::disk('local');
}
public function all()
{
$searchers = [];
$files = $this->storage->files('searchers');
foreach ($files as $file) {
if (pathinfo($file, PATHINFO_EXTENSION) === 'json') {
$name = pathinfo($file, PATHINFO_FILENAME);
$result = explode('_', $name);
if (count($result) === 2) {
$searchers[$name] = [
'id' => $result[0],
'name' => $result[1],
];
}
}
}
return $searchers;
}
public function has($id)
{
return $this->storage->exists("searchers/$id.json");
}
public function get($id)
{
$contents = $this->storage->get("searchers/$id.json");
return json_decode($contents, true);
}
}

51585
package-lock.json
File diff suppressed because it is too large
View File

3
package.json

@ -23,7 +23,8 @@
"ts-loader": "^8.1.0",
"typescript": "~3.8.3",
"vue-loader": "^15.9.6",
"vue-template-compiler": "^2.6.12"
"vue-template-compiler": "^2.6.12",
"webpack": "^5.9.0"
},
"dependencies": {
"primeflex": "^2.0.0",

890
public/css/app.css
File diff suppressed because it is too large
View File

17342
public/js/app.js
File diff suppressed because it is too large
View File

23
resources/js/app.ts

@ -20,16 +20,17 @@ import Skeleton from 'primevue/skeleton';
import ToastService from 'primevue/toastservice';
import Toast from 'primevue/toast';
import Fieldset from 'primevue/fieldset';
import SelectButton from 'primevue/selectbutton';
// Own components
import AppHeader from './components/layout/Header.vue';
import AppFooter from './components/layout/Footer.vue';
import Home from './components/Home.vue';
import ProcessFile from './components/ProcessFile.vue';
import Filter from './components/helpers/Filter.vue';
import SelectButton from 'primevue/selectbutton';
import Home from './components/Home.vue';
import RegexCreate from './components/Regex/Create.vue';
import SearchersCreate from './components/Searchers/Create.vue';
import SearchersShow from './components/Searchers/Show.vue';
Vue.use(PrimeVue, {
ripple: true,
@ -53,11 +54,21 @@ Vue.component('Toast', Toast);
Vue.component('SelectButton', SelectButton);
Vue.component('Fieldset', Fieldset);
// Layout
Vue.component('app-header', AppHeader);
Vue.component('app-footer', AppFooter);
// Views
Vue.component('home', Home);
Vue.component('regex-create', RegexCreate);
Vue.component('searchers-create', SearchersCreate);
Vue.component('searchers-show', SearchersShow);
// Includes
Vue.component('process-file', ProcessFile);
Vue.component('filter-view', Filter);
Vue.component('app-header', AppHeader);
Vue.component('app-footer', AppFooter);
new Vue({
el: '#app',

4
resources/js/bootstrap.ts

@ -10,6 +10,8 @@ import Cookie from './SearchDisplace/Cookie';
import axios from "axios";
(window as any).axios = axios;
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token: HTMLMetaElement | null = document.head.querySelector('meta[name="csrf-token"]');
@ -30,4 +32,4 @@ if (token) {
// return request;
// }, (error) => {
// return Promise.reject(error)
// });
// });

2
resources/js/components/ProcessFile.vue

@ -1,5 +1,4 @@
<template>
<div class="p-d-flex p-flex-row p-jc-between p-ai-stretch">
<Panel
class="p-mr-2 p-as-stretch file-card"
@ -46,7 +45,6 @@
</template>
<script lang="ts">
import axios from 'axios';
import { Vue, Component, Prop } from 'vue-property-decorator';
import { FileData } from '../interfaces/FileData';
import { FilterInterface } from '../interfaces/FilterInterface';

69
resources/js/components/Regex/Create.vue

@ -0,0 +1,69 @@
<template>
<div id="regex-create">
<div>
<div>
<input v-model="name"
type="text"
placeholder="Enter searcher name"
class="input">
</div>
<button @click="onSave" :disabled=" ! name || ! pattern">
Save
</button>
</div>
<div class="regex-box">
<div class="main">
<pattern-box v-model="pattern"></pattern-box>
<text-box :pattern="pattern"
:flags="flags">
</text-box>
<flags v-model="flags"></flags>
</div>
<aside>
<side-bar></side-bar>
</aside>
</div>
</div>
</template>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
import TextBox from './TextBox.vue';
import PatternBox from './PatternBox.vue';
import Flags from './Flags.vue';
import SideBar from './SideBar.vue';
@Component({
components: {
TextBox,
PatternBox,
Flags,
SideBar,
},
})
export default class Create extends Vue {
private name: string = '';
private pattern: string = '';
private flags: Array<string> = ['g', 'i'];
async onSave() {
try {
const { data } = await (window as any).axios.post('/regex', {
name: this.name,
expression: this.pattern,
});
alert('Saved.');
} catch (e) {
console.log(e);
console.log('Something went wrong.');
}
}
};
</script>

46
resources/js/components/Regex/Flags.vue

@ -0,0 +1,46 @@
<template>
<div id="flags" class="flags">
<div class="input-group">
<label for="global_match">
<input type="checkbox"
value="g"
v-model="flags"
id="global_match"> global match
</label>
</div>
<div class="input-group">
<label for="ignore_case">
<input type="checkbox"
value="i"
v-model="flags"
id="ignore_case"> case insensitive
</label>
</div>
</div>
</template>
<script lang="ts">
import {Component, Prop, Watch, Vue} from "vue-property-decorator";
@Component
export default class Flags extends Vue {
private flags: Array<string> = ['g'];
@Prop() public readonly value!: Array<string>;
@Watch('flags')
flagsChanged(update: Array<string>) {
this.$emit('input', update);
}
@Watch('value')
valueChanged(update: Array<string>) {
this.flags = update;
}
created() {
this.flags = this.value;
}
};
</script>

45
resources/js/components/Regex/PatternBox.vue

@ -0,0 +1,45 @@
<template>
<div id="pattern-box" class="pattern-box-wrapper">
<input autofocus
v-model="pattern"
placeholder="Enter pattern"
class="pattern-box"
type="text">
<p v-show="error" class="error-text">{{ error }}</p>
</div>
</template>
<script lang="ts">
import {Component, Prop, Watch, Vue} from "vue-property-decorator";
@Component
export default class PatternBox extends Vue {
private pattern: string = '';
private error: string = '';
@Prop({default: ''}) public readonly value: string = '';
@Watch('pattern')
patternChanged(value: string) {
try {
new RegExp(value);
this.$emit('input', value);
this.error = '';
} catch (error) {
this.error = 'Expression is invalid';
}
}
@Watch('value')
valueChanged(value: string) {
this.pattern = value
}
created() {
this.pattern = this.value
}
};
</script>

225
resources/js/components/Regex/SideBar.vue

@ -0,0 +1,225 @@
<template>
<div id="references" class="references">
<div class="reference">
<h3 class="title mb">Metacharacters</h3>
<h4 class="caption mb">Metacharacters express sets of characters or special characters.</h4>
<ul class="characters">
<li class="character mb">
<p class="character__code">
<span class="purple">.</span>
</p>
<p class="character__info">
<span>any character</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span>^</span>
</p>
<p class="character__info">
<span>beginning of a line</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span>$</span>
</p>
<p class="character__info">
<span>end of a line</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span>\</span>
</p>
<p class="character__info">
<span>quotes special characters <br>(
<span class="purple">* ? + [ ] ( ) { } ^ $ | \ . /</span> )
</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span class="blue">\w</span>
</p>
<p class="character__info">
<span>word</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span class="blue">\t</span>
</p>
<p class="character__info">
<span>horizontal tabulation</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span class="blue">\n</span>
</p>
<p class="character__info">
<span>new line</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span class="blue">\d</span>
</p>
<p class="character__info">
<span>any digit</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span class="blue">\t</span>
</p>
<p class="character__info">
<span>white space character (
<span class="blue">\t \n \f \r \{Z}</span>)</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span class="blue">[...]</span>
</p>
<p class="character__info">
<span>match any character (or range of characters) inside the bracket. Range may be e.g.
<span class="blue">[a-z], [A-Z], [3...5]</span>, etc.</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span class="blue">\D</span>
</p>
<p class="character__info">
<span>any character that is not a decimal digit</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span class="blue">\S</span>
</p>
<p class="character__info">
<span>non-whitespace character</span>
</p>
</li>
<li class="character">
<p class="character__code">
<span class="blue">\W</span>
</p>
<p class="character__info">
<span>non-word character</span>
</p>
</li>
</ul>
</div>
<div class="reference">
<h3 class="title mb">Operators</h3>
<h4 class="caption mb">Operators allow to desribe how an expression (or subexpression) should be matched.</h4>
<ul class="characters">
<li class="character mb">
<p class="character__code">
<span>(
<span class="white"></span>)</span>
</p>
<p class="character__info">
<span>groups expression into subexpressions</span>
</p>
</li>
<li class="character mb">
<p class="character__code">
<span class="white">A
<span class="purple">|</span> B</span>
</p>
<p class="character__info">
<span>groups expression into subexpressions</span>
</p>
</li>
<li class="character mb">
<p class="character__code">
<span class="purple">^</span>
</p>
<p class="character__info">
<span>negation</span>
</p>
</li>
<li class="character mb">
<p class="character__code">
<span class="purple">*</span>
</p>
<p class="character__info">
<span>match 0 or more times</span>
</p>
</li>
<li class="character mb">
<p class="character__code">
<span class="purple">+</span>
</p>
<p class="character__info">
<span>match 1 or more times</span>
</p>
</li>
<li class="character mb">
<p class="character__code">
<span class="purple">?</span>
</p>
<p class="character__info">
<span>match 0 or 1 time</span>
</p>
</li>
<li class="character mb">
<p class="character__code">
<span class="purple">{
<span class="white">n</span> }</span>
</p>
<p class="character__info">
<span>match exactly n times</span>
</p>
</li>
<li class="character mb">
<p class="character__code">
<span class="purple">{
<span class="white">n
<span class="purple">,</span> m</span> }</span>
</p>
<p class="character__info">
<span>match exactly n times</span>
</p>
</li>
</ul>
</div>
<div class="reference">
<h3 class="title mb">Non-capturing operators</h3>
<h4 class="caption mb">These operators work by one simple rule. They are not captured in groups.</h4>
<ul class="characters">
<li class="character mb">
<p class="character__code">
<span class="disabled">( ? # ... )</span>
</p>
<p class="character__info">
<span>comment</span>
</p>
</li>
<li class="character mb">
<p class="character__code">
<span class="purple">( ? :
<span class="white"></span> )</span>
</p>
<p class="character__info">
<span>Subexpression must occur but it's not captured in group.</span>
</p>
</li>
<li class="character mb">
<p class="character__code">
<span class="purple">( ? !
<span class="white"></span> )</span>
</p>
<p class="character__info">
<span>Makes sure that the subexpression does not occur at current position. It's useful to exclude part of expression.</span>
</p>
</li>
</ul>
</div>
</div>
</template>

39
resources/js/components/Regex/TextBox.vue

@ -0,0 +1,39 @@
<template>
<div id="text-box" class="text-box">
<div ref="backdrop" class="backdrop">
<div class="matches" v-html="matches"></div>
</div>
<textarea ref="text"
v-model="text"
placeholder="Enter text to check matches"
@scroll="handleScroll">
</textarea>
</div>
</template>
<script lang="ts">
import {Component, Prop, Vue} from "vue-property-decorator";
@Component
export default class TextBox extends Vue {
private text: string = '';
@Prop({default: ''}) public readonly pattern: string = '';
@Prop() public readonly flags!: Array<string>;
getMatches() {
return this.text
.replace(/\n$/g, '\n\n')
.replace(new RegExp(this.pattern, this.flags.join('')), '<mark>$&</mark>');
}
handleScroll() {
// this.$refs.backdrop.scrollTop = this.$refs.text.scrollTop;
}
get matches(): string {
return this.getMatches();
}
};
</script>

62
resources/js/components/Searchers/AddBox.vue

@ -0,0 +1,62 @@
<template>
<div>
<div @click="adding = true"
class="box"
style="font-size: 6rem;">
+
</div>
<Dialog header="Select searcher"
position="right"
:visible="adding"
:closable="false"
:style="{width: '50vw'}"
:modal="true">
<searchers @selected="onSearcherSelected"></searchers>
<template #footer>
<Button label="Close"
icon="pi pi-times"
@click="adding = false"
class="p-button-text"/>
<Button label="Confirm"
icon="pi pi-check"
@click="onConfirm"
:disabled="Object.keys(selectedSearcher).length === 0"
autofocus />
</template>
</Dialog>
</div>
</template>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
import Dialog from 'primevue/dialog';
import Searchers from './Index.vue';
@Component({
name: 'AddBox',
components: {
Dialog,
Searchers,
},
})
export default class Create extends Vue {
private adding: boolean = false;
private selectedSearcher: Object = {};
onSearcherSelected(searcher: Object) {
this.selectedSearcher = searcher;
}
onConfirm() {
this.$emit('added', this.selectedSearcher);
this.selectedSearcher = {};
this.adding = false;
}
};
</script>

65
resources/js/components/Searchers/Create.vue

@ -0,0 +1,65 @@
<template>
<div id="searchers-create">
<div>
<input v-model="name"
type="text"
placeholder="Enter searcher name"
class="input">
<button @click="onSave" :disabled=" ! name || rows.length === 0">
Save
</button>
</div>
<div v-for="(row, rowIndex) in rows" :key="`row-${rowIndex}`" class="flex-row">
<div v-for="(searcher, columnIndex) in row" :key="`column-${columnIndex}`">
<div class="box">
{{ searcher.name }}
</div>
</div>
<add-box @added="(searcher) => { onSearcherAdded(searcher, rowIndex); }">
</add-box>
</div>
<add-box @added="onNewRowSearcherAdded">
</add-box>
</div>
</template>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
import AddBox from './AddBox.vue';
@Component({
components: {
AddBox,
},
})
export default class Create extends Vue {
private name: String = '';
private rows: Array<Array<Object>> = [];
onNewRowSearcherAdded(searcher: Object) {
const length = this.rows.push([]);
this.onSearcherAdded(searcher, length - 1);
}
onSearcherAdded(searcher: Object, rowIndex: number) {
this.rows[rowIndex].push(searcher);
}
async onSave() {
try {
const { data } = await (window as any).axios.post('/searchers', {
name: this.name,
rows: this.rows,
});
} catch (e) {
console.log(e);
console.log('Something went wrong.');
}
}
};
</script>

65
resources/js/components/Searchers/Index.vue

@ -0,0 +1,65 @@
<template>
<div style="display: flex; flex-direction: row;">
<div v-for="(searcher, id) in searchers"
:key="id"
@click="onSelect(id)"
class="box flex-column"
:class="{selected: id === selectedSearcherId}"
style="margin-left: 1rem;">
<div>
<span> {{ searcher.name }} </span>
</div>
<div style="margin-top: 5px; color: dodgerblue;">
<a @click.stop="onOpen(id)"> View </a>
</div>
</div>
</div>
</template>
<script lang="ts">
import {Component, Vue} from "vue-property-decorator";
@Component({
})
export default class Create extends Vue {
private searchers: any = {};
private selectedSearcherId: string = '';
async boot() {
try {
const { data } = await (window as any).axios.get('/searchers');
this.searchers = data.searchers;
} catch (e) {
}
}
onSelect(id: string) {
if (this.selectedSearcherId === id) {
this.selectedSearcherId = '';
this.$emit('selected', {});
return;
}
this.selectedSearcherId = id;
this.$emit('selected', this.searchers[id]);
}
onOpen(id: string) {
window.open(this.getURL(id), '_blank');
}
getURL(id: string) {
return `/searchers/${id}`;
}
created() {
this.boot();
}
};
</script>

50
resources/js/components/Searchers/Show.vue

@ -0,0 +1,50 @@
<template>
<div id="searchers-show">
<div>
<h3>{{ searcher.name }}</h3>
<h5>{{ searcher.description }}</h5>
<a v-if="editable"
:href="`/searchers/${searcher.id}/edit`">
Edit
</a>
</div>
<div v-for="(row, rowIndex) in searcher.rows" :key="`row-${rowIndex}`" class="flex-row">
<div v-for="(searcherItem, columnIndex) in row" :key="`column-${columnIndex}`">
<div class="box is-plain">
<template v-if="searcherItem.hasOwnProperty('name')">
<searcher-show :editable="false"
:searcher="searcherItem">
</searcher-show>
</template>
<template v-else>
<b>{{ searcherItem.expression }}</b>
<p>
<!-- // Show example here, so for example the user has to input the example in order to save-->
<!-- // the regex, show highlight, so apply regex on example text-->
</p>
</template>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import {Component, Vue, Prop} from "vue-property-decorator";
@Component({
name: 'SearcherShow',
})
export default class Create extends Vue {
@Prop({default: {}})
public readonly searcher!: Object;
@Prop({default: true})
public readonly editable!: boolean;
};
</script>

54
resources/sass/_layout.sass

@ -0,0 +1,54 @@
@import "variables"
.page-wrapper
flex: 1
.header
// background: #ffffff
background: $header-background
height: $header-height
padding: 0 2rem
border-bottom: 1px solid #dee2e6
display: -ms-flexbox
display: flex
-ms-flex-align: center
align-items: center
-ms-flex-pack: justify
justify-content: space-between
color: #495057
.left
display: -ms-flexbox
display: flex
-ms-flex-align: center
align-items: center
.footer
// background: #ffffff
background: $footer-background
height: $footer-height
border-top: 1px solid #dee2e6
border-bottom: 1px solid #dee2e6
padding: 0 2rem
display: -ms-flexbox
display: flex
-ms-flex-align: center
align-items: center
-ms-flex-pack: justify
justify-content: space-between
flex-shrink: 0
.left
display: -ms-flexbox
display: flex
-ms-flex-align: center
align-items: center
.right
font-size: 0.875rem
color: #6c757d
.content
padding: $content-padding

60
resources/sass/_layout.scss

@ -1,60 +0,0 @@
@import "variables";
.page-wrapper {
flex: 1;
}
.header {
// background: #ffffff;
background: $header-background;
height: $header-height;
padding: 0 2rem;
border-bottom: 1px solid #dee2e6;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between;
color: #495057;
.left {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
}
}
.footer {
// background: #ffffff;
background: $footer-background;
height: $footer-height;
border-top: 1px solid #dee2e6;
border-bottom: 1px solid #dee2e6;
padding: 0 2rem;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: justify;
justify-content: space-between;
flex-shrink: 0;
.left {
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
}
.right {
font-size: 0.875rem;
color: #6c757d;
}
}
.content {
padding: $content-padding;
}

7
resources/sass/_variables.sass

@ -0,0 +1,7 @@
$header-height: 4rem
$header-background: var(--blue-400)
$footer-height: 4rem
$footer-background: var(--blue-400)
$content-padding: 25px

7
resources/sass/_variables.scss

@ -1,7 +0,0 @@
$header-height: 4rem;
$header-background: var(--blue-400);
$footer-height: 4rem;
$footer-background: var(--blue-400);
$content-padding: 25px;

79
resources/sass/app.sass

@ -0,0 +1,79 @@
@import '~primeflex/src/_variables'
@import '~primeflex/src/_grid'
@import '~primeflex/src/_formlayout'
@import '~primeflex/src/_display'
@import '~primeflex/src/_text'
@import '~primeflex/src/flexbox/_flexbox'
@import '~primeflex/src/_spacing'
@import '~primeflex/src/_elevation'
// @import '~primevue/resources/themes/fluent-light/theme.css'
// @import '~primevue/resources/themes/vela-green/theme.css'
@import '~primevue/resources/themes/mdc-light-indigo/theme.css'
@import '~primevue/resources/primevue.min.css'
@import '~primeicons/primeicons.css'
@import 'layout'
@import "components/index"
body
height: 100%
margin: 0
background: #f8f9fa
#app
font-family: Avenir, Helvetica, Arial, sans-serif
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
text-align: center
color: #2c3e50
display: flex
flex-direction: column
min-height: 100vh
#nav
padding: 30px
a
font-weight: bold
color: #2c3e50
&.router-link-exact-active
color: #42b983
// Temp location
.box
border: 1px solid black
min-width: 100px
min-height: 100px
padding: 10px
display: flex
justify-content: center
align-items: center
cursor: pointer
margin-left: 1rem
margin-bottom: 1rem
&.selected
border-color: dodgerblue
&.is-plain
cursor: default
&.auto
width: auto
min-width: 100px
.flex-row
display: flex
flex-direction: row
.flex-column
display: flex
flex-direction: column
.flex-center
display: flex
align-items: center
justify-content: center

48
resources/sass/app.scss

@ -1,48 +0,0 @@
@import '~primeflex/src/_variables';
@import '~primeflex/src/_grid';
@import '~primeflex/src/_formlayout';
@import '~primeflex/src/_display';
@import '~primeflex/src/_text';
@import '~primeflex/src/flexbox/_flexbox';
@import '~primeflex/src/_spacing';
@import '~primeflex/src/_elevation';
// @import '~primevue/resources/themes/fluent-light/theme.css';
// @import '~primevue/resources/themes/vela-green/theme.css';
@import '~primevue/resources/themes/mdc-light-indigo/theme.css';
@import '~primevue/resources/primevue.min.css';
@import '~primeicons/primeicons.css';
@import 'layout';
body {
height: 100%;
margin: 0;
background: #f8f9fa;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
display: flex;
flex-direction: column;
min-height: 100vh;
}
#nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}

1
resources/sass/components/_index.sass

@ -0,0 +1 @@
@import "regex/index"

1
resources/sass/components/regex/_index.sass

@ -0,0 +1 @@
@import 'create/index'

9
resources/sass/components/regex/create/_flags.sass

@ -0,0 +1,9 @@
#flags.flags
display: flex
border-top: 1px solid #f7f7f72d
padding: 0.8rem
color: #d6d7cc
.input-group
margin-right: 1.2rem
font-size: 12px

43
resources/sass/components/regex/create/_index.sass

@ -0,0 +1,43 @@
@import 'pattern-box'
@import 'flags'
@import 'text-box'
@import 'references'
#regex-create
.regex-box
background-color: #001221
display: flex
width: 850px
height: 500px
margin: 5rem auto
position: relative
border-radius: 5px
transition: all 0.25s linear
.regex-box:hover
box-shadow: 0.4rem 1.4rem 1.4rem rgba(0, 0, 0, 0.2)
transform: translateY(-10px)
.main
display: flex
flex-direction: column
width: 550px
aside
width: 300px
padding: 1.5rem
border-left: 1px solid #ffffff31
overflow-y: scroll
aside::-webkit-scrollbar
background: #3f4545
width: 10px !important
aside::-webkit-scrollbar-track
border-radius: 10px !important
aside::-webkit-scrollbar-thumb
border-radius: 10px !important
-webkit-box-shadow: inset 0 0 6px rgba(54, 52, 52, 0.925) !important
box-shadow: inset 0 0 6px rgba(54, 52, 52, 0.863) !important

26
resources/sass/components/regex/create/_pattern-box.sass

@ -0,0 +1,26 @@
#pattern-box
.pattern-box
width: 100%
font: inherit
min-height: 46px
background: transparent
color: #d6d7cc
padding: 2.3rem 25px
border: 0
outline: none
.error-text
position: absolute
width: 100%
bottom: 0
color: #d6d7cc
font-size: 12px
text-align: center
letter-spacing: 1px
input::placeholder
color: grey
#pattern-box.pattern-box-wrapper
position: relative
border-bottom: 1px solid #f7f7f72d !important

63
resources/sass/components/regex/create/_references.sass

@ -0,0 +1,63 @@
#references.references
.reference:not(:first-child)
margin-top: 1.6rem
.reference *
color: #fff
font-size: 12px
font-weight: inherit
margin: 0
padding: 0
.mb
margin-bottom: 0.5rem
.title
font-size: 17px
ul,
li
list-style: none
.character
display: flex
.character:not(:last-child)
margin-bottom: 15px
.character__code
background: #00080e
border: 1px solid #f7f7f72d
padding: 0.3rem 0.8rem
display: flex
justify-content: center
align-items: center
margin-right: 0.5rem
.character__code span
white-space: nowrap
font-weight: 600
color: #c77ce0
.character__info
display: flex
align-items: center
padding-bottom: 5px
.disabled,
.purple,
.blue,
.white
font-weight: 600
.disabled
color: #888888 !important
.purple
color: #c77ce0 !important
.blue
color: #3c7fc2 !important
.white
color: white !important

63
resources/sass/components/regex/create/_text-box.sass

@ -0,0 +1,63 @@
#text-box.text-box
flex-grow: 1
width: 100%
overflow: hidden
position: relative
background-color: #011627
.text-box *
box-sizing: border-box
.backdrop,
textarea
position: absolute
padding: 1.5rem 25px 8px
width: 100%
height: 100%
font-size: inherit
background-color: transparent
outline: none
border: 0
overflow: auto
letter-spacing: 1px
.backdrop
z-index: 1
pointer-events: none
.matches,
textarea
font: inherit
textarea
display: block
position: absolute
z-index: 2
resize: none
color: #fff
textarea::-webkit-scrollbar
background: #282a36
width: 17px !important
textarea::-webkit-scrollbar-track
border-radius: 10px !important
textarea::-webkit-scrollbar-thumb
border-radius: 10px !important
textarea::placeholder
color: grey
.matches
white-space: pre-wrap
word-wrap: break-word
color: transparent
/* dynamically generated content style with deep selector */
.matches mark
color: transparent
background-color: #9e3cc0
.matches mark:nth-child(even)
background-color: #3c7fc2

10
resources/views/app.blade.php

@ -13,9 +13,15 @@
<body>
<div id="app">
{{-- <navbar></navbar>--}}
<app-header></app-header>
@yield('content')
<div class="page-wrapper">
<div class="content">
@yield('content')
</div>
</div>
<app-footer></app-footer>
</div>
<script src="{{ mix('js/app.js') }}" defer></script>

12
resources/views/pages/home.blade.php

@ -1,15 +1,5 @@
@extends('app')
@section('content')
<div class="page-wrapper">
<app-header></app-header>
<div class="content">
<home :filters="{{ json_encode($filters) }}"></home>
</div>
</div>
<app-footer></app-footer>
<home :filters="{{ json_encode($filters) }}"></home>
@endsection

5
resources/views/pages/regex/create.blade.php

@ -0,0 +1,5 @@
@extends('app')
@section('content')
<regex-create></regex-create>
@endsection

5
resources/views/pages/searchers/create.blade.php

@ -0,0 +1,5 @@
@extends('app')
@section('content')
<searchers-create></searchers-create>
@endsection

7
resources/views/pages/searchers/show.blade.php

@ -0,0 +1,7 @@
@extends('app')
@section('content')
<searchers-show :searcher="{{ json_encode($searcher) }}"></searchers-show>
@endsection

13
routes/web.php

@ -14,14 +14,17 @@ use Illuminate\Support\Facades\Route;
|
*/
Route::get('/', 'HomeController@index');
Route::get('/search-and-displace/{id}', 'SearchAndDisplaceController@show');
Route::post('/search-and-displace', 'SearchAndDisplaceController@store');
Route::get('/', [
PagesController::class,
'home'
]);
Route::get('/search-and-displace/{id}', 'SearchAndDisplaceController@show');
Route::post('/search-and-displace', 'SearchAndDisplaceController@store');
Route::get('/regex/create', 'RegexController@create');
Route::post('/regex', 'RegexController@store');
Route::resource('/searchers', 'SearcherController');
Route::webhooks('/webhooks', 'default');

2
webpack.mix.js

@ -23,4 +23,4 @@ mix.webpackConfig({
mix.ts('resources/js/app.ts', 'public/js')
.vue()
.sass('resources/sass/app.scss', 'public/css');
.sass('resources/sass/app.sass', 'public/css');

10190
yarn.lock
File diff suppressed because it is too large
View File

Loading…
Cancel
Save