YAML tester
YAML tester to lekka aplikacja webowa umożliwiająca szybkie testowanie i debugowanie plików YAML bez potrzeby uruchamiania lokalnego środowiska.
Zbudowana w oparciu o bibliotekę Spyc (https://github.com/mustangostang/spyc), parser YAML dla PHP, oferuje zgodność z najczęściej używaną składnią YAML 1.2, obsługującą m.in.:
- zagnieżdżone struktury tablic i obiektów,
- mapy klucz-wartość,
- typy skalarne i sekwencje,
- wcięcia i białe znaki zgodne z konwencją YAML.
Funkcje:
- Parser oparty na Spyc – szybki, stabilny i battle-tested.
- Bezpieczne środowisko testowe – kod nie jest zapisywany ani przesyłany dalej.
- Informacja zwrotna w czasie rzeczywistym – błędy składni zwracane natychmiast.
- Zgodność z PHP – wyniki walidacji odpowiadają strukturze, jaką otrzymasz przy użyciu
Spyc::YAMLLoad()
.
Dla kogo?
Dla programistów PHP, DevOpsów, administratorów systemów i wszystkich, którzy chcą błyskawicznie zweryfikować poprawność i strukturę danych YAML przed wdrożeniem ich do aplikacji.
Przykładowy plik YAML do testowania
app:
name: YAML tester
version: 1.0.0
author: Jan Kowalski
features:
- Walidacja składni
- Parser oparty na Spyc
- Obsługa struktur zagnieżdżonych
- Przejrzyste błędy
database:
host: localhost
port: 3306
username: root
password: secret
options:
reconnect: true
timeout: 30
debug: true
Wynik
{{ result }}
Kod po stronie przeglądarki
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.7/codemirror.min.css">
<link rel="stylesheet" href="http://www.dariuszrorat.ugu.pl/assets/css/codemirror/theme/golden.css">
<link rel="stylesheet" href="http://www.dariuszrorat.ugu.pl/assets/css/codemirror/themes.css">
<div id="app">
<div class="row mb-3">
<div class="col-12">
<label for="yaml" class="form-label">Tekst YAML</label>
<textarea id="yaml" class="form-control"></textarea>
</div>
</div>
<div class="row mb-3">
<div class="col-3">
<label for="format" class="form-label">Format wyjściowy</label>
<select id="format" class="form-select" v-model="format">
<option value="php">PHP</option>
<option value="json">JSON</option>
</select>
</div>
</div>
<div class="mb-3">
<button class="btn btn-primary" @click="convert"><i class="bi bi-play-fill"></i> Wykonaj</button>
</div>
<div class="mb-3" v-if="success">
<h2>Wynik</h2>
<pre><code :class="'language-'+format" id="output">{{ result }}</code></pre>
</div>
<div class="progress mb-3" v-if="success">
<div class="progress-bar" role="progressbar" :style="'width: '+ percent + '%'" :class="bgColor">
{{time}} ms
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.7/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.7/mode/yaml/yaml.min.js"></script>
<script>
// Max 10ms expected time
const MAX_EXPECTED_TIME = 10;
function escapeHTML(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
var vm = null;
document.addEventListener('DOMContentLoaded', function() {
vm = new Vue({
el: '#app',
data: {
yaml: '',
format: 'php',
result: '',
success: false,
time: 0,
bgColor: 'text-bg-primary',
percent: 0
},
updated() {
this.$nextTick(function () {
const el = document.getElementById('output');
if (el)
{
if (el.hasAttribute('data-highlighted'))
el.removeAttribute('data-highlighted');
if (el.hasAttribute('data-highlighter'))
el.removeAttribute('data-highlighter');
if (el.classList.contains('hljs'))
el.classList.remove(...Array.from(el.classList).filter(cls => cls.startsWith('hljs')));
el.innerHTML = escapeHTML(el.textContent);
hljs.highlightElement(el);
}
});
},
methods: {
convert() {
var self = this;
self.result = '';
var data = {
yaml: self.yaml,
format: self.format,
token: '1f9de1297e2a533b016362d4774ecfd674749377'
};
self.success = false;
axios.post(urlSite('public/ajax/app/exec/27'), data, {headers: {'Content-Type': 'application/x-www-form-urlencoded'}})
.then(function (response) {
self.result = response.data.data;
const duration = response.data.time;
self.time = duration.toFixed(2);
self.percent = Math.min((duration / MAX_EXPECTED_TIME) * 100, 100).toFixed(0);
if (self.percent <= 25) self.bgColor = 'text-bg-success';
else if ((self.percent > 25) && (self.percent <= 50)) self.bgColor = 'text-bg-info';
else if ((self.percent > 50) && (self.percent <= 75)) self.bgColor = 'text-bg-warning';
else self.bgColor = 'text-bg-danger';
self.success = true;
}).catch(function(error) {
self.success = false;
BootstrapToast.show({title: 'Błąd', message: 'Przepraszamy, ale wystąpił błąd parsowania składni.', when: 'teraz', type: 'text-bg-danger'});
});
}
}
});
//CodeMirror for textarea
var myTextarea = document.getElementById('yaml');
var editor = CodeMirror.fromTextArea(myTextarea, {
lineNumbers: true,
matchBrackets: true,
mode: "text/x-yaml",
indentUnit: 4,
indentWithTabs: false,
enterMode: "keep",
tabMode: "shift",
extraKeys: {Tab: false, "Shift-Tab": false}
});
editor.setSize(null, 500);
editor.getWrapperElement().style["font-size"] = "14px";
editor.refresh();
editor.on('change', (args) => { vm.yaml = editor.getValue() } );
//WCAG missing form label fix
codemirrorTextareaIdFix("yaml");
});
</script>
Kod po stronie serwera
$yaml = Input::post('yaml', '');
$format = Input::post('format', 'php');
$timediff = 0;
try
{
$start = microtime(true);
$data = Format::from_yaml($yaml);
$end = microtime(true);
$timediff = ($end - $start) * 1000;
}
catch (Exception $e)
{
Kohana_Exception::log($e);
throw HTTP_Exception::factory(500)
->as_json();
}
$dump = $format === 'php' ? var_export($data, true) : $data;
$output = [
'data' => $dump,
'time' => $timediff,
];
$response = Response::factory()
->headers('Content-Type', 'application/json')
->status(200)
->body(json_encode($output));
echo $response->send_headers()->body();