diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..f0e209f
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..9173912
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/nur-sery.iml b/.idea/nur-sery.iml
new file mode 100644
index 0000000..5d55ddf
--- /dev/null
+++ b/.idea/nur-sery.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php-docker-settings.xml b/.idea/php-docker-settings.xml
new file mode 100644
index 0000000..bd786be
--- /dev/null
+++ b/.idea/php-docker-settings.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/php.xml b/.idea/php.xml
new file mode 100644
index 0000000..31dc4ce
--- /dev/null
+++ b/.idea/php.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/phpunit.xml b/.idea/phpunit.xml
new file mode 100644
index 0000000..4f8104c
--- /dev/null
+++ b/.idea/phpunit.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.keep b/src/.keep
deleted file mode 100644
index e69de29..0000000
diff --git a/src/output/Output.php b/src/output/Output.php
new file mode 100644
index 0000000..a716ae1
--- /dev/null
+++ b/src/output/Output.php
@@ -0,0 +1,22 @@
+printCsv($object);
+ else $this->printContent([$object]);
+ }
+}
diff --git a/src/output/TtyOutput.php b/src/output/TtyOutput.php
new file mode 100644
index 0000000..61c1ee9
--- /dev/null
+++ b/src/output/TtyOutput.php
@@ -0,0 +1,20 @@
+value =& $value;
+ }
+
+ protected $value;
+
+ function exists($key=null): bool {
+ if ($key === null) return true;
+ return $this->value !== null && array_key_exists($key, $this->value);
+ }
+
+ function get($key=null) {
+ if ($key === null) return $this->value;
+ elseif ($this->value === null) return null;
+ elseif (!array_key_exists($key, $this->value)) return null;
+ else return $this->value[$key];
+ }
+
+ function set($value, $key=null): void {
+ if ($key === null) $this->value = $value;
+ else $this->value[$key] = $value;
+ }
+}
diff --git a/src/schema/PostInput.php b/src/schema/PostInput.php
new file mode 100644
index 0000000..2955aff
--- /dev/null
+++ b/src/schema/PostInput.php
@@ -0,0 +1,21 @@
+schema = $schema;
+ }
+
+ /**
+ * @var array schéma normalisé
+ *
+ * il y a 3 formes pour un schéma:
+ * - schéma valeur scalaire [0 => type, 1 => default, ...]
+ * - schéma tableau séquentiel [schema]
+ * - schéma tableau associatif [key => schema, ...]
+ */
+ protected $schema;
+
+ /**
+ * retourner true si le schéma est pour une valeur scalaire, false s'il s'agit
+ * du schéma d'un tableau (séquentiel ou associatif)
+ */
+ function isScalar(): bool {
+ return array_key_exists(0, $this->schema) && array_key_exists(1, $this->schema);
+ }
+
+ /** retourner true si le schéma est pour un tableau séquentiel */
+ function isSeq(): bool {
+ return array_key_exists(0, $this->schema) && !array_key_exists(1, $this->schema);
+ }
+
+ /** retourner true si le schéma est pour un tableau associatif */
+ function isAssoc(): bool {
+ return !array_key_exists(0, $this->schema) && !array_key_exists(1, $this->schema);
+ }
+
+ /**
+ * @var bool true si toutes les clés du schéma doivent exister, avec leur
+ * valeur par défaut le cas échéant
+ */
+ protected $ppEnsureKeys;
+
+ /** @var bool true si les clés doivent être dans l'ordre du schéma */
+ protected $ppOrderKeys;
+
+ /**
+ * @var bool true si les valeurs doivent être automatiquement vérifiées et
+ * corrigées
+ */
+ protected $ppVerifix;
+
+ function ensureSchema(&$value, $key=null, ?array $params=null): void {
+ }
+
+ /** retourner true si la valeur est scalaire */
+ function getScalar(&$value, $key=null, ?ScalarValue &$scalar=null): bool {
+ if (!$this->isScalar()) return false;
+ if ($value instanceof Input) $input = $value;
+ else $input = new Input($value);
+ $scalar = new ScalarValue($input, $this->schema, $key);
+ return true;
+ }
+
+ /** retourner true la valeur est un tableau séquentiel */
+ function getSeq(&$value, $key=null, ?SeqValue &$seq=null): bool {
+ if (!$this->isSeq()) return false;
+ if ($value instanceof Input) $input = $value;
+ else $input = new Input($value);
+ $seq = new SeqValue($input, $this->schema, $key);
+ return true;
+ }
+
+ /** retourner true la valeur est un tableau associatif */
+ function getAssoc(&$value, $key=null, ?AssocValue &$assoc=null): bool {
+ if (!$this->isAssoc()) return false;
+ if ($value instanceof Input) $input = $value;
+ else $input = new Input($value);
+ $assoc = new AssocValue($input, $this->schema, $key);
+ return true;
+ }
+
+ function getValue(&$value, $key=null): IValue {
+ if ($this->getScalar($input, $key, $scalar)) return $scalar;
+ elseif ($this->getSeq($input, $key, $seq)) return $seq;
+ elseif ($this->getAssoc($input, $key, $assoc)) return $assoc;
+ else throw StateException::unexpected_state();
+ }
+}
diff --git a/src/schema/SchemaException.php b/src/schema/SchemaException.php
new file mode 100644
index 0000000..bef20fd
--- /dev/null
+++ b/src/schema/SchemaException.php
@@ -0,0 +1,7 @@
+ ["string", null, "" => 0,
+ "required" => true,
+ "desc" => "type de la valeur",
+ ],
+ "default" => [null, null, "" => 1,
+ "desc" => "valeur par défaut",
+ ],
+ "key" => ["?key", null, "" => "",
+ "desc" => "clé de la valeur si elle est dans un tableau",
+ ],
+ "required" => ["bool", false,
+ "desc" => "cette valeur est-elle requise?",
+ ],
+ "formatter" => ["?callable", null,
+ "desc" => "signature de la fonction: (value, format, type) => string",
+ ],
+ "parser" => ["?callable", null,
+ "desc" => "signature de la fonction: (value, type) => [value, result]",
+ ],
+ "messages" => ["?array", null,
+ "desc" => "message à afficher en cas d'erreur d'analyse",
+ ],
+ ];
+
+ const MESSAGES = [
+ "absent" => "{key}: Vous devez spécifier cette valeur",
+ "null" => "{key}: Cette valeur ne doit pas être nulle",
+ "invalid" => "{key}: {orig}: cette valeur est invalide",
+ ];
+}
diff --git a/src/schema/types/IType.php b/src/schema/types/IType.php
new file mode 100644
index 0000000..6820ee3
--- /dev/null
+++ b/src/schema/types/IType.php
@@ -0,0 +1,25 @@
+ false];
+ foreach (static::KEYS as $key) {
+ if (!array_key_exists($key, $result)) $result[$key] = null;
+ }
+ #XXX interpoler [message] avec les clés de $result
+ $this->result = $result;
+ }
+
+ protected $result;
+
+ function __get($name) {
+ return $this->result[$name];
+ }
+}
diff --git a/src/schema/values/ScalarValue.php b/src/schema/values/ScalarValue.php
new file mode 100644
index 0000000..6704947
--- /dev/null
+++ b/src/schema/values/ScalarValue.php
@@ -0,0 +1,74 @@
+schema = $schema;
+ $this->reset($value, $key, $verifix);
+ }
+
+ function isScalar(?ScalarValue &$scalar=null): bool { $scalar = $this; return true; }
+ function isSeq(?SeqValue &$seq=null): bool { return false; }
+ function isAssoc(?AssocValue &$assoc=null): bool { return false; }
+
+ function get($default=null) {
+ $key = $this->key;
+ if ($key === null) return $this->value;
+ elseif (array_key_exists($key, $this->value)) return $this->value[$key];
+ else return $default;
+ }
+
+ function set($value): self {
+ $key = $this->key;
+ if ($key === null) $this->value = $value;
+ else $this->value[$key] = $value;
+ return $this;
+ }
+
+ /** @var IType */
+ protected $type;
+
+ function type(): IType {
+ if ($this->type === null) $this->type = $this->md()->getType($this->key);
+ return $this->type;
+ }
+
+ /** @var ?Result résultat de l'analyse de la valeur */
+ protected $result;
+
+ function exists(): bool {
+ return $this->type()->exists($this->value, $this->key);
+ }
+
+ /**
+ * analyser, corriger éventuellement et normaliser la valeur
+ *
+ * si la valeur était déjà normalisée, retourner false.
+ */
+ function verifix(bool $throw=true, ?Result &$result=null): bool {
+ $type = $this->type();
+ $key = $this->key;
+ if ($key === null) $modified = $type->verifix($this->value, $throw, $result);
+ else $modified = $type->verifix($this->value[$key], $throw, $result);
+ $this->result = $result;
+ return $modified;
+ }
+
+ function parse($value, bool $throw=true, ?Result &$result=null) {
+ $this->type()->verifix($value, $throw, $result);
+ $this->set($value);
+ $this->result = $result;
+ return $value;
+ }
+
+ function format(?string $format=null): string {
+ $type = $this->type();
+ $key = $this->key;
+ if ($key === null) return $type->format($this->value, $format);
+ else return $type->format($this->value[$key], $format);
+ }
+}
diff --git a/src/schema/values/SeqValue.php b/src/schema/values/SeqValue.php
new file mode 100644
index 0000000..c72ca81
--- /dev/null
+++ b/src/schema/values/SeqValue.php
@@ -0,0 +1,20 @@
+md === null) $this->md = Schema::with($this->schema);
+ return $this->md;
+ }
+
+ /** @var mixed valeur ou référence d'un tableau contenant la valeur */
+ protected $value;
+
+ /** @var string|int clé de la valeur dans le tableau destination */
+ protected $key;
+
+ function reset(&$value, $key=null, bool $verifix=true): void {
+ $this->value =& $value;
+ $this->key = $key;
+ $this->result = null;
+ if ($verifix) $this->verifix();
+ }
+}