almost completed doc
vijayphoenix
4 years ago
0 | 0 | ## 0.2.0.0 |
1 | 1 | |
2 | * Changes in `Data.YAML` module | |
3 | * YAML 1.2 Schema encoders (#21) | |
4 | * New `ToYAML` class for encoding Haskell Data-types from which YAML nodes can be constructed (#20) | |
5 | * New functions like `encodeNode`, `encodeNode'` for constructing AST. | |
6 | * New functions like `encode`, `encode1`, `encodeStrict`, `encode1Strict` for supporting typeclass-based dumping | |
7 | * Some ToYAML instances and other api | |
8 | * Modify `typeMismatch` function to show error source location in error messages (#19) | |
9 | ||
2 | 10 | * Changes in `Data.YAML.Event` module |
11 | * Preserve and round-trip Comments at Event level(#24) | |
12 | * New `Comment` Event to preserve comments while parsing | |
13 | * Some additional implementations to preserve and round-trip comments | |
14 | * Fix issue #22 | |
15 | * New `EvPos` type for recording event and their corresponding position (#19) | |
16 | * Preserve Flow Mapping and Flow sequence (#18) | |
17 | * Features to preserve Literal/Folded ScalarStyle (#15) | |
18 | * New `Chomp` type denoting Block Chomping Indicator | |
19 | * New `IndentOfs` type denoting Block Indentation Indicator | |
3 | 20 | * New `NodeStyle` type denoting flow/block style |
4 | 21 | * `Event(SequenceStart,MappingStart)` constructors now record `NodeStyle` |
5 | 22 | * `Style` type renamed to `ScalarType` |
12 | 29 | duplicate keys (under the respective YAML schema) in YAML mappings |
13 | 30 | as a loader-error (controllable via new |
14 | 31 | `schemaResolverMappingDuplicates` schema property) |
32 | ||
33 | * Fix X38W testcase (#13, #14) | |
15 | 34 | |
16 | 35 | --- |
17 | 36 |
1 | 1 | name: HsYAML |
2 | 2 | version: 0.2.0.0 |
3 | 3 | |
4 | synopsis: Pure Haskell YAML 1.2 parser | |
4 | synopsis: Pure Haskell YAML 1.2 parsing and encoding library | |
5 | 5 | homepage: https://github.com/hvr/HsYAML |
6 | 6 | bug-reports: https://github.com/hvr/HsYAML/issues |
7 | 7 | license: GPL-2 |
16 | 16 | tested-with: GHC==8.6.1, GHC==8.4.3, GHC==8.4.1, GHC==8.2.2, GHC==8.0.2, GHC==7.10.3, GHC==7.8.4, GHC==7.6.3, GHC==7.4.2 |
17 | 17 | |
18 | 18 | description: |
19 | @HsYAML@ is a [YAML 1.2](http://yaml.org/spec/1.2/spec.html) parser implementation for Haskell. | |
19 | @HsYAML@ is a [YAML 1.2](http://yaml.org/spec/1.2/spec.html) parsing and encoding library. | |
20 | 20 | . |
21 | 21 | Features of @HsYAML@ include: |
22 | 22 | . |
12 | 12 | , toList |
13 | 13 | ) where |
14 | 14 | |
15 | -- | A difference list is a function that, given a list, returns the original | |
16 | -- contents of the difference list prepended to the given list. | |
15 | 17 | newtype DList a = DList ([a] -> [a]) |
16 | 18 | |
19 | -- | Convert a dlist to a list | |
17 | 20 | toList :: DList a -> [a] |
18 | 21 | toList (DList dl) = dl [] |
19 | 22 | |
23 | -- | Create dlist with a single element | |
20 | 24 | singleton :: a -> DList a |
21 | 25 | singleton x = DList (x:) |
22 | 26 | |
27 | -- | Create a dlist containing no elements | |
23 | 28 | empty :: DList a |
24 | 29 | empty = DList id |
25 | 30 | |
31 | -- | O(1). Append dlists | |
26 | 32 | append :: DList a -> DList a -> DList a |
27 | 33 | append (DList xs) (DList ys) = DList (xs . ys) |
2 | 2 | {-# LANGUAGE BangPatterns #-} |
3 | 3 | {-# LANGUAGE RecordWildCards #-} |
4 | 4 | |
5 | ||
5 | -- | | |
6 | -- Copyright: © Herbert Valerio Riedel 2015-2018 | |
7 | -- SPDX-License-Identifier: GPL-2.0-or-later | |
8 | -- | |
6 | 9 | module Data.YAML.Dumper |
7 | 10 | ( encodeNode |
8 | 11 | , encodeNode' |
17 | 20 | import qualified Data.Map as Map |
18 | 21 | import qualified Data.Text as T |
19 | 22 | |
20 | ||
23 | -- internal | |
21 | 24 | type EvList = [Either String Event] |
22 | 25 | type Node2EvList = [Node ()] -> EvList |
23 | 26 |
37 | 37 | -- document ::= 'DocumentStart' node 'DocumentEnd' |
38 | 38 | -- node ::= 'Alias' |
39 | 39 | -- | 'Scalar' |
40 | -- | 'Comment' | |
40 | 41 | -- | sequence |
41 | 42 | -- | mapping |
42 | 43 | -- sequence ::= 'SequenceStart' node* 'SequenceEnd' |
4 | 4 | -- Copyright: © Herbert Valerio Riedel 2015-2018 |
5 | 5 | -- SPDX-License-Identifier: GPL-2.0-or-later |
6 | 6 | -- |
7 | -- Event-stream oriented YAML parsing API | |
7 | -- Event-stream oriented YAML parsing and serializing API | |
8 | 8 | module Data.YAML.Event |
9 | ( parseEvents | |
9 | ( | |
10 | -- * Tutorial | |
11 | -- $start | |
12 | ||
13 | -- ** Parsing YAML Documents | |
14 | -- $parsing | |
15 | parseEvents | |
16 | ||
17 | -- ** Serializing Events to YAML Character Stream | |
18 | -- $serialize | |
10 | 19 | , writeEvents |
11 | 20 | , writeEventsText |
21 | ||
22 | -- ** How to comment your yaml document for best results | |
23 | -- $commenting | |
24 | ||
25 | -- ** Event-stream Internals | |
12 | 26 | , EvStream |
13 | 27 | , Event(..) |
14 | 28 | , EvPos(..) |
15 | 29 | , Directives(..) |
16 | 30 | , ScalarStyle(..) |
17 | 31 | , NodeStyle(..) |
32 | , Chomp(..) | |
33 | , IndentOfs(..) | |
18 | 34 | , Tag, untagged, isUntagged, tagToText, mkTag |
19 | 35 | , Anchor |
20 | 36 | , Pos(..) |
54 | 70 | mkTag'' "" = error "mkTag''" |
55 | 71 | mkTag'' s = Tag (Just $! T.pack ("tag:yaml.org,2002:" ++ s)) |
56 | 72 | |
57 | ||
73 | -- Returns the position corresponding to the 'Token' | |
58 | 74 | tok2pos :: Y.Token -> Pos |
59 | 75 | tok2pos Y.Token { Y.tByteOffset = posByteOffset, Y.tCharOffset = posCharOffset, Y.tLine = posLine, Y.tLineChar = posColumn } = Pos {..} |
60 | 76 | |
77 | -- Construct a 'EvPos' from the given 'Event' and 'Pos' | |
61 | 78 | getEvPos :: Event -> Y.Token -> EvPos |
62 | 79 | getEvPos ev tok = EvPos { eEvent = ev , ePos = tok2pos tok } |
63 | 80 | |
81 | -- Initial position('Pos' corresponding to the 'StreamStart') | |
64 | 82 | initPos :: Pos |
65 | 83 | initPos = Pos { posByteOffset = 0 , posCharOffset = 0 , posLine = 1 , posColumn = 0 } |
66 | 84 | |
85 | -- Initial 'EvPos' | |
67 | 86 | initEvPos :: Event -> EvPos |
68 | 87 | initEvPos ev = EvPos { eEvent = ev, ePos = initPos } |
69 | 88 | |
89 | -- Pair Event with the corresponding Event-Start Position. | |
70 | 90 | delayPos :: Pos -> EvStream -> EvStream |
71 | 91 | delayPos _ [] = [] |
72 | 92 | delayPos pos (Right EvPos{ eEvent = a ,ePos = nextPos } : rest ) = (Right EvPos{ eEvent = a , ePos = pos}: delayPos nextPos rest) |
109 | 129 | |
110 | 130 | -- | Parse YAML 'Event's from a lazy 'BS.L.ByteString'. |
111 | 131 | -- |
132 | -- The parsed Events allow us to round-trip at the event-level while preserving many features and presentation details like | |
133 | -- 'Comment's,'ScalarStyle','NodeStyle', 'Anchor's, 'Directives' marker along with YAML document version, | |
134 | -- 'Chomp'ing Indicator,Indentation Indicator ('IndentOfs') ,ordering, etc. | |
135 | -- It does not preserve non-content white spaces. | |
136 | -- | |
112 | 137 | -- The input 'BS.L.ByteString' is expected to have a YAML 1.2 stream |
113 | 138 | -- using the UTF-8, UTF-16 (LE or BE), or UTF-32 (LE or BE) encodings |
114 | 139 | -- (which will be auto-detected). |
140 | ||
115 | 141 | parseEvents :: BS.L.ByteString -> EvStream |
116 | 142 | parseEvents = \bs0 -> delayPos initPos $ Right (initEvPos StreamStart) : (go0 $ filter (not . isWhite) $ Y.tokenize bs0 False) |
117 | 143 | where |
498 | 524 | ] |
499 | 525 | unescape _ = Nothing |
500 | 526 | |
527 | -- | |
528 | -- $start | |
529 | -- | |
530 | -- "Data.YAML" module provides us with API which allow us to interact with YAML data at the cost of some presentation details. | |
531 | -- In contrast, this module provide us with API which gives us access to a other significant details like 'ScalarStyle's, 'NodeStyle's, 'Comment's, etc. | |
532 | -- | |
533 | -- $parsing | |
534 | -- | |
535 | -- Suppose you want to parse this YAML Document while preserving its format and comments | |
536 | -- | |
537 | -- @ | |
538 | -- # Home runs | |
539 | -- hr: 65 | |
540 | -- # Runs Batted In | |
541 | -- rbi: 147 | |
542 | -- @ | |
543 | -- | |
544 | -- then you might want to use the function 'parseEvents'. | |
545 | -- | |
546 | -- The following is a reference implementation of a function using 'parseEvents'. | |
547 | -- It takes a YAML document as input and prints the parsed YAML 'Event's. | |
548 | -- | |
549 | -- @ | |
550 | -- import Data.YAML.Event | |
551 | -- import qualified Data.ByteString.Lazy as BS.L | |
552 | -- | |
553 | -- printEvents :: BS.L.ByteString -> IO () | |
554 | -- printEvents input = | |
555 | -- forM_ ('parseEvents' input) $ \ev -> case ev of | |
556 | -- Left _ -> error "Failed to parse" | |
557 | -- Right event -> print ('eEvent' event) | |
558 | -- @ | |
559 | -- | |
560 | -- When we pass the above mentioned YAML document to the function /printEvents/ it outputs the following: | |
561 | -- | |
562 | -- > StreamStart | |
563 | -- > DocumentStart NoDirEndMarker | |
564 | -- > MappingStart Nothing Nothing Block | |
565 | -- > Comment " Home runs" | |
566 | -- > Scalar Nothing Nothing Plain "hr" | |
567 | -- > Scalar Nothing Nothing Plain "65" | |
568 | -- > Comment " Runs Batted In" | |
569 | -- > Scalar Nothing Nothing Plain "rbi" | |
570 | -- > Scalar Nothing Nothing Plain "147" | |
571 | -- > MappingEnd | |
572 | -- > DocumentEnd False | |
573 | -- > StreamEnd | |
574 | -- | |
575 | -- Notice that now we have all the necessary details in the form of 'Event's. | |
576 | -- | |
577 | -- We can now write simple functions to work with this data without losing any more details. | |
578 | -- | |
579 | -- $serialize | |
580 | -- | |
581 | -- Now, suppose we want to generate back the YAML document after playing with the Event-stream, | |
582 | -- then you might want to use 'writeEvents'. | |
583 | -- | |
584 | -- The following function takes a YAML document as a input and dumps it back to STDOUT after a round-trip. | |
585 | -- | |
586 | -- @ | |
587 | -- import Data.YAML.Event | |
588 | -- import qualified Data.YAML.Token as YT | |
589 | -- import qualified Data.ByteString.Lazy as BS.L | |
590 | -- | |
591 | -- yaml2yaml :: BS.L.ByteString -> IO () | |
592 | -- yaml2yaml input = case sequence $ parseEvents input of | |
593 | -- Left _ -> error "Parsing Failure!" | |
594 | -- Right events -> do | |
595 | -- BS.L.hPutStr stdout (writeEvents YT.UTF8 (map eEvent events)) | |
596 | -- hFlush stdout | |
597 | -- @ | |
598 | -- | |
599 | -- Let this be the sample document passed to the above function | |
600 | -- | |
601 | -- @ | |
602 | -- # This is a 'Directives' Marker | |
603 | -- --- | |
604 | -- # All 'Comment's are preserved | |
605 | -- date : 2019-07-12 | |
606 | -- bill-to : # 'Anchor' represents a map node | |
607 | -- &id001 | |
608 | -- address: | |
609 | -- lines: # This a Block 'Scalar' with 'Keep' chomping Indicator and 'IndentAuto' Indentant indicator | |
610 | -- |+ # Extra Indentation (non-content white space) will not be preserved | |
611 | -- Vijay | |
612 | -- IIT Hyderabad | |
613 | -- | |
614 | -- | |
615 | -- # Trailing newlines are a preserved here as they are a part of the 'Scalar' node | |
616 | -- country : India | |
617 | -- ship-to : # This is an 'Alias' | |
618 | -- *id001 | |
619 | -- # Key is a 'Scalar' and Value is a Sequence | |
620 | -- Other Details: | |
621 | -- total: $ 3000 | |
622 | -- # 'Tag's are also preserved | |
623 | -- Online Payment: !!bool True | |
624 | -- product: | |
625 | -- - Item1 | |
626 | -- # This comment is inside a Sequence | |
627 | -- - Item2 | |
628 | -- ... | |
629 | -- # 'DocumentEnd' True | |
630 | -- # 'StreamEnd' | |
631 | -- @ | |
632 | -- | |
633 | -- This function outputs the following | |
634 | -- | |
635 | -- @ | |
636 | -- # This is a 'Directives' Marker | |
637 | -- --- | |
638 | -- # All 'Comment's are preserved | |
639 | -- date: 2019-07-12 | |
640 | -- bill-to: # 'Anchor' represents a map node | |
641 | -- &id001 | |
642 | -- address: | |
643 | -- lines: # This a Block 'Scalar' with 'Keep' chomping Indicator and 'IndentAuto' Indentant indicator | |
644 | -- # Extra Indentation (non-content white space) will not be preserved | |
645 | -- |+ | |
646 | -- Vijay | |
647 | -- IIT Hyderabad | |
648 | -- | |
649 | -- | |
650 | -- # Trailing newlines are a preserved here as they are a part of the 'Scalar' node | |
651 | -- country: India | |
652 | -- ship-to: # This is an 'Alias' | |
653 | -- *id001 | |
654 | -- # Key is a 'Scalar' and Value is a Sequence | |
655 | -- Other Details: | |
656 | -- total: $ 3000 | |
657 | -- # 'Tag's are also preserved | |
658 | -- Online Payment: !!bool True | |
659 | -- product: | |
660 | -- - Item1 | |
661 | -- # This comment is inside a Sequence | |
662 | -- - Item2 | |
663 | -- ... | |
664 | -- # 'DocumentEnd' True | |
665 | -- # 'StreamEnd' | |
666 | -- @ | |
667 | -- | |
668 | -- $commenting | |
669 | -- | |
670 | -- Round-tripping at event-level will preserve all the comments and their relative position in the YAML-document but still, | |
671 | -- we lose some information like the exact indentation and the position at which the comments were present previously. | |
672 | -- This information sometimes can be quiet important for human-perception of comments. | |
673 | -- Below are some guildlines, so that you can avoid ambiguities. | |
674 | -- | |
675 | -- 1) Always try to start your comment in a newline. This step will avoid most of the ambiguities. | |
676 | -- | |
677 | -- 2) Comments automaticly get indented according to the level in which they are present. For example, | |
678 | -- | |
679 | -- Input YAML-document | |
680 | -- | |
681 | -- @ | |
682 | -- # Level 0 | |
683 | -- - a | |
684 | -- # Level 0 | |
685 | -- - - a | |
686 | -- # Level 1 | |
687 | -- - a | |
688 | -- - - a | |
689 | -- # Level 2 | |
690 | -- - a | |
691 | -- @ | |
692 | -- | |
693 | -- After a round-trip looks like | |
694 | -- | |
695 | -- @ | |
696 | -- # Level 0 | |
697 | -- - a | |
698 | -- # Level 0 | |
699 | -- - - a | |
700 | -- # Level 1 | |
701 | -- - a | |
702 | -- - - a | |
703 | -- # Level 2 | |
704 | -- - a | |
705 | -- @ | |
706 | -- | |
707 | -- 3) Comments immediately after a 'Scalar' node, start from a newline. So avoid commenting just after a scalar ends, as it may lead to some ambiguity. For example, | |
708 | -- | |
709 | -- Input YAML-document | |
710 | -- | |
711 | -- @ | |
712 | -- - scalar # After scalar | |
713 | -- - random : scalar # After scalar | |
714 | -- key: 1 | |
715 | -- # not after scalar | |
716 | -- - random : scalar | |
717 | -- key: 1 | |
718 | -- - random : # not after scalar | |
719 | -- scalar | |
720 | -- # not after scalar | |
721 | -- key: 1 | |
722 | -- @ | |
723 | -- | |
724 | -- After a round-trip looks like | |
725 | -- | |
726 | -- @ | |
727 | -- - scalar | |
728 | -- # After scalar | |
729 | -- - random: scalar | |
730 | -- # After scalar | |
731 | -- key: 1 | |
732 | -- # not after scalar | |
733 | -- - random: scalar | |
734 | -- key: 1 | |
735 | -- - random: # not after scalar | |
736 | -- scalar | |
737 | -- # not after scalar | |
738 | -- key: 1 | |
739 | -- @ | |
740 | -- | |
741 | -- 4) Similarly in flow-style, avoid commenting immediately after a /comma/ (@,@) seperator. Comments immediately after a /comma/ (@,@) seperator will start from a new line | |
742 | -- | |
743 | -- Input YAML-document | |
744 | -- | |
745 | -- @ | |
746 | -- { | |
747 | -- # comment 0 | |
748 | -- Name: Vijay # comment 1 | |
749 | -- , | |
750 | -- # comment 2 | |
751 | -- age: 19, # comment 3 | |
752 | -- # comment 4 | |
753 | -- country: India # comment 5 | |
754 | -- } | |
755 | -- @ | |
756 | -- | |
757 | -- After a round-trip looks like | |
758 | -- | |
759 | -- @ | |
760 | -- { | |
761 | -- # comment 0 | |
762 | -- Name: Vijay, | |
763 | -- # comment 1 | |
764 | -- # comment 2 | |
765 | -- age: 19, | |
766 | -- # comment 3 | |
767 | -- # comment 4 | |
768 | -- country: India, | |
769 | -- # comment 5 | |
770 | -- } | |
771 | -- @ | |
772 | -- | |
773 | -- 5) Avoid commenting in between syntatical elements. For example, | |
774 | -- | |
775 | -- Input YAML-document | |
776 | -- | |
777 | -- @ | |
778 | -- ? # Complex key starts | |
779 | -- [ | |
780 | -- a, | |
781 | -- b | |
782 | -- ] | |
783 | -- # Complex key ends | |
784 | -- : # Complex Value starts | |
785 | -- ? # Complex key starts | |
786 | -- [ | |
787 | -- a, | |
788 | -- b | |
789 | -- ] | |
790 | -- # Complex key ends | |
791 | -- : # Simple value | |
792 | -- a | |
793 | -- # Complex value ends | |
794 | -- @ | |
795 | -- | |
796 | -- After a round-trip looks like | |
797 | -- | |
798 | -- @ | |
799 | -- ? # Complex key starts | |
800 | -- [ | |
801 | -- a, | |
802 | -- b | |
803 | -- ] | |
804 | -- : # Complex key ends | |
805 | -- # Complex Value starts | |
806 | -- | |
807 | -- ? # Complex key starts | |
808 | -- [ | |
809 | -- a, | |
810 | -- b | |
811 | -- ] | |
812 | -- : # Complex key ends | |
813 | -- # Simple value | |
814 | -- a | |
815 | -- # Complex value ends | |
816 | -- @ | |
817 | -- | |
818 | -- The above two YAML-documents, after parsing produce the same 'Event'-stream. | |
819 | -- | |
820 | -- So, these are some limitation of this Format-preserving YAML processor.⏎ |
0 | 0 | {-# LANGUAGE Safe #-} |
1 | 1 | |
2 | -- | | |
3 | -- Copyright: © Herbert Valerio Riedel 2015-2018 | |
4 | -- SPDX-License-Identifier: GPL-2.0-or-later | |
5 | -- | |
2 | 6 | module Data.YAML.Internal |
3 | 7 | ( Node(..) |
4 | 8 | , NodeId |
343 | 343 | , ("-.INF", (-1/0)) |
344 | 344 | ] |
345 | 345 | |
346 | ||
346 | -- | Some tags specified in YAML 1.2 | |
347 | 347 | tagNull, tagBool, tagStr, tagInt, tagFloat, tagSeq, tagMap, tagBang :: Tag |
348 | 348 | tagNull = mkTag "tag:yaml.org,2002:null" |
349 | 349 | tagStr = mkTag "tag:yaml.org,2002:str" |
8 | 8 | -- SPDX-License-Identifier: GPL-2.0-or-later |
9 | 9 | -- |
10 | 10 | -- Document oriented [YAML](http://yaml.org/spec/1.2/spec.html) parsing API inspired by [aeson](http://hackage.haskell.org/package/aeson). |
11 | -- | |
12 | -- === Overview | |
11 | ||
12 | module Data.YAML | |
13 | ( | |
14 | ||
15 | -- * Overview | |
16 | -- $overview | |
17 | ||
18 | -- * Quick Start Tutorial | |
19 | -- $start | |
20 | ||
21 | -- ** Decoding/Loading YAML document | |
22 | -- $loading | |
23 | ||
24 | -- ** Encoding/dumping | |
25 | -- $dumping | |
26 | ||
27 | -- * Typeclass-based resolving/decoding | |
28 | decode | |
29 | , decode1 | |
30 | , decodeStrict | |
31 | , decode1Strict | |
32 | , FromYAML(..) | |
33 | , Parser | |
34 | , parseEither | |
35 | , typeMismatch | |
36 | ||
37 | -- ** Accessors for YAML 'Mapping's | |
38 | , Mapping | |
39 | , (.:), (.:?), (.:!), (.!=) | |
40 | ||
41 | -- * Typeclass-based dumping | |
42 | , encode | |
43 | , encode1 | |
44 | , encodeStrict | |
45 | , encode1Strict | |
46 | , ToYAML(..) | |
47 | ||
48 | -- ** Accessors for encoding | |
49 | , mapping | |
50 | , (.=) | |
51 | ||
52 | -- ** Prism-style parsers | |
53 | , withSeq | |
54 | , withBool | |
55 | , withFloat | |
56 | , withInt | |
57 | , withNull | |
58 | , withStr | |
59 | , withMap | |
60 | ||
61 | -- * \"Concrete\" AST | |
62 | , decodeNode | |
63 | , decodeNode' | |
64 | , encodeNode | |
65 | , encodeNode' | |
66 | , Doc(Doc) | |
67 | , Node(..) | |
68 | , Scalar(..) | |
69 | ||
70 | -- * YAML 1.2 Schema resolvers | |
71 | , SchemaResolver(..) | |
72 | , failsafeSchemaResolver | |
73 | , jsonSchemaResolver | |
74 | , coreSchemaResolver | |
75 | ||
76 | -- * YAML 1.2 Schema encoders | |
77 | , SchemaEncoder(..) | |
78 | , failsafeSchemaEncoder | |
79 | , jsonSchemaEncoder | |
80 | , coreSchemaEncoder | |
81 | ||
82 | -- * Generalised AST construction | |
83 | , decodeLoader | |
84 | , Loader(..) | |
85 | , NodeId | |
86 | ||
87 | ) where | |
88 | ||
89 | import qualified Control.Monad.Fail as Fail | |
90 | import qualified Data.ByteString as BS | |
91 | import qualified Data.ByteString.Lazy as BS.L | |
92 | import qualified Data.Map as Map | |
93 | import Data.Maybe (listToMaybe) | |
94 | import qualified Data.Text as T | |
95 | ||
96 | import Data.YAML.Dumper | |
97 | import Data.YAML.Event (Pos (..), isUntagged, tagToText) | |
98 | import Data.YAML.Internal | |
99 | import Data.YAML.Loader | |
100 | import Data.YAML.Schema | |
101 | ||
102 | import Util | |
103 | ||
104 | -- $overview | |
13 | 105 | -- |
14 | 106 | -- The diagram below depicts the standard layers of a [YAML 1.2](http://yaml.org/spec/1.2/spec.html) processor. This module covers the upper /Native/ and /Representation/ layers, whereas the "Data.YAML.Event" and "Data.YAML.Token" modules provide access to the lower /Serialization/ and /Presentation/ layers respectively. |
15 | 107 | -- |
16 | 108 | -- <<http://yaml.org/spec/1.2/overview2.png>> |
17 | 109 | -- |
18 | -- === Quick Start Tutorial | |
110 | -- $start | |
111 | -- | |
112 | -- This section contains basic information on the different ways to work with YAML data using this library. | |
113 | -- | |
114 | -- $loading | |
115 | -- | |
116 | -- We address the process of loading data from a YAML document as decoding. | |
19 | 117 | -- |
20 | 118 | -- Let's assume we want to decode (i.e. /load/) a simple YAML document |
21 | 119 | -- |
50 | 148 | -- >>> decode "- name: Erik Weisz\n age: 52\n magic: True\n- name: Mina Crandon\n age: 53" :: Either String [[Person]] |
51 | 149 | -- Right [[Person {name = "Erik Weisz", age = 52, magic = True},Person {name = "Mina Crandon", age = 53, magic = False}]] |
52 | 150 | -- |
53 | -- | |
54 | module Data.YAML | |
55 | ( | |
56 | -- * Typeclass-based dumping | |
57 | encode | |
58 | , encode1 | |
59 | , encodeStrict | |
60 | , encode1Strict | |
61 | , ToYAML(..) | |
62 | ||
63 | -- * Typeclass-based resolving/decoding | |
64 | , decode | |
65 | , decode1 | |
66 | , decodeStrict | |
67 | , decode1Strict | |
68 | , FromYAML(..) | |
69 | , Parser | |
70 | , parseEither | |
71 | , typeMismatch | |
72 | ||
73 | -- ** Accessors for YAML 'Mapping's | |
74 | , Mapping | |
75 | , (.:), (.:?), (.:!), (.!=) | |
76 | ||
77 | , mapping | |
78 | , (.=) | |
79 | ||
80 | -- ** Prism-style parsers | |
81 | , withSeq | |
82 | , withBool | |
83 | , withFloat | |
84 | , withInt | |
85 | , withNull | |
86 | , withStr | |
87 | , withMap | |
88 | ||
89 | -- * \"Concrete\" AST | |
90 | , decodeNode | |
91 | , decodeNode' | |
92 | , encodeNode | |
93 | , encodeNode' | |
94 | , Doc(Doc) | |
95 | , Node(..) | |
96 | , Scalar(..) | |
97 | ||
98 | -- * YAML 1.2 Schema resolvers | |
99 | , SchemaResolver(..) | |
100 | , failsafeSchemaResolver | |
101 | , jsonSchemaResolver | |
102 | , coreSchemaResolver | |
103 | ||
104 | -- * YAML 1.2 Schema encoders | |
105 | , SchemaEncoder(..) | |
106 | , failsafeSchemaEncoder | |
107 | , jsonSchemaEncoder | |
108 | , coreSchemaEncoder | |
109 | ||
110 | -- * Generalised AST construction | |
111 | , decodeLoader | |
112 | , Loader(..) | |
113 | , NodeId | |
114 | ||
115 | ) where | |
116 | ||
117 | import qualified Control.Monad.Fail as Fail | |
118 | import qualified Data.ByteString as BS | |
119 | import qualified Data.ByteString.Lazy as BS.L | |
120 | import qualified Data.Map as Map | |
121 | import Data.Maybe (listToMaybe) | |
122 | import qualified Data.Text as T | |
123 | ||
124 | import Data.YAML.Dumper | |
125 | import Data.YAML.Event (Pos (..), isUntagged, tagToText) | |
126 | import Data.YAML.Internal | |
127 | import Data.YAML.Loader | |
128 | import Data.YAML.Schema | |
129 | ||
130 | import Util | |
151 | -- There are predefined 'FromYAML' instance for many types. | |
152 | -- | |
153 | -- The example below shows decoding multiple YAML documents into a list of 'Int' lists: | |
154 | -- | |
155 | -- >>> decode "---\n- 1\n- 2\n- 3\n---\n- 4\n- 5\n- 6" :: Either String [[Int]] | |
156 | -- Right [[1,2,3],[4,5,6]] | |
157 | -- | |
158 | -- If you are expecting exactly one YAML document then you can use convenience function 'decode1' | |
159 | -- | |
160 | -- >>> decode1 "- 1\n- 2\n- 3\n" :: Either String [Int] | |
161 | -- Right [1,2,3] | |
162 | -- | |
163 | -- == Working with AST | |
164 | -- | |
165 | -- Sometimes we want to work with YAML data directly, without first converting it to a custom data type. | |
166 | -- | |
167 | -- We can easily do that by using the 'Node' type, which is an instance of 'FromYAML', is used to represent an arbitrary YAML AST (abstract syntax tree). For example, | |
168 | -- | |
169 | -- >>> decode1 "Name: Vijay" :: Either String (Node Pos) | |
170 | -- Right (Mapping (Pos {posByteOffset = 0, posCharOffset = 0, posLine = 1, posColumn = 0}) Just "tag:yaml.org,2002:map" (fromList [(Scalar (Pos {posByteOffset = 0, posCharOffset = 0, posLine = 1, posColumn = 0}) (SStr "Name"),Scalar (Pos {posByteOffset = 4, posCharOffset = 4, posLine = 1, posColumn = 4}) (SStr "Vijay"))])) | |
171 | -- | |
172 | -- The type parameter 'Pos' is used to indicate the position of each YAML 'Node' in the document. | |
173 | -- So using the 'Node' type we can easily decode any YAML document. | |
174 | ||
131 | 175 | |
132 | 176 | -- | Retrieve value in 'Mapping' indexed by a @!!str@ 'Text' key. |
133 | 177 | -- |
511 | 555 | |
512 | 556 | |
513 | 557 | |
558 | -- $dumping | |
559 | -- | |
560 | -- We address the process of dumping information from a Haskell-data type(s) to a YAML document(s) as encoding. | |
561 | -- | |
562 | -- Suppose we want to encode a Haskell-data type Person | |
563 | -- @ | |
564 | -- data Person = Person { | |
565 | -- name :: Text | |
566 | -- , age :: Int | |
567 | -- } deriving Show | |
568 | -- | |
569 | -- To encode data, we need to define a 'ToYAML' instance. | |
570 | -- @ | |
571 | -- @ | |
572 | -- instance 'ToYAML' Person where | |
573 | -- \-- this generates a 'Node' | |
574 | -- 'toYAML' (Person n a) = 'mapping' [ "name" .= n, "age" .= a] | |
575 | -- @ | |
576 | -- We can now encode a value like so: | |
577 | -- | |
578 | -- >>> encode [Person {name = "Vijay", age = 19}) | |
579 | -- "age: 19\nname: Vijay\n" | |
580 | -- | |
581 | -- There are predefined 'ToYAML' instances for many types. Here's an example encoding a complex Haskell Node' | |
582 | -- | |
583 | -- >>> encode1 $ toYAML ([1,2,3],Data.Map.fromList [(1, 2)]) | |
584 | -- "- - 1\n - 2\n - 3\n- 1: 2\n" | |
585 | -- | |
586 | ||
587 | ||
514 | 588 | -- | A type from which YAML nodes can be constructed |
515 | 589 | -- |
516 | 590 | -- @since 0.2.0.0 |
60 | 60 | |
61 | 61 | |
62 | 62 | #if !MIN_VERSION_base(4,6,0) |
63 | ||
64 | -- | Parse a string using the Read instance. Succeeds if there is | |
65 | -- exactly one valid result. | |
63 | 66 | readMaybe :: Read a => String -> Maybe a |
64 | 67 | readMaybe = either (const Nothing) id . readEither |
65 | 68 | |
69 | -- | Parse a string using the Read instance. Succeeds if there is | |
70 | -- exactly one valid result. A Left value indicates a parse error. | |
66 | 71 | readEither :: Read a => String -> Either String a |
67 | 72 | readEither s = case [ x | (x,"") <- readPrec_to_S read' minPrec s ] of |
68 | 73 | [x] -> Right x |
74 | 79 | return x |
75 | 80 | #endif |
76 | 81 | |
77 | ||
82 | -- | Succeeds if the Integral value is in the bounds of the given Data type. | |
83 | -- A Nothing indicates that the value is outside the bounds. | |
78 | 84 | fromIntegerMaybe :: forall n . (Integral n, Bounded n) => Integer -> Maybe n |
79 | 85 | fromIntegerMaybe j |
80 | 86 | | l <= j, j <= u = Just (fromInteger j) |
84 | 90 | l = toInteger (minBound :: n) |
85 | 91 | |
86 | 92 | |
87 | ||
93 | -- | A convience wrapper over 'mapInsertNoDupe' | |
88 | 94 | mapFromListNoDupes :: Ord k => [(k,a)] -> Either (k,a) (Map k a) |
89 | 95 | mapFromListNoDupes = go mempty |
90 | 96 | where |
93 | 99 | Nothing -> Left (k,v) |
94 | 100 | Just m' -> go m' rest |
95 | 101 | |
102 | -- | A convience wrapper over Data.Map.insertLookupWithKey | |
96 | 103 | mapInsertNoDupe :: Ord k => k -> a -> Map k a -> Maybe (Map k a) |
97 | 104 | mapInsertNoDupe kx x t = case Map.insertLookupWithKey (\_ a _ -> a) kx x t of |
98 | 105 | (Nothing, m) -> Just m |
99 | 106 | (Just _, _) -> Nothing |
100 | 107 | |
101 | 108 | |
109 | -- | Equivalent to the fuction Data.ByteString.toStrict. | |
110 | -- O(n) Convert a lazy ByteString into a strict ByteString. | |
102 | 111 | {-# INLINE bsToStrict #-} |
103 | 112 | bsToStrict :: BS.L.ByteString -> BS.ByteString |
104 | 113 | #if MIN_VERSION_bytestring(0,10,0) |
174 | 174 | Left (ofs,msg) -> do |
175 | 175 | hPutStrLn stderr ("Parsing error near byte offset " ++ show ofs ++ if null msg then "" else " (" ++ msg ++ ")") |
176 | 176 | exitFailure |
177 | Right event -> do | |
178 | print (eEvent event) | |
177 | Right event -> print (eEvent event) | |
179 | 178 | -- hPutStrLn stdout (ev2str True (eEvent event)) |
180 | 179 | -- hFlush stdout |
181 | 180 |