gongju-text (empty) → 0.1.0.0
raw patch · 8 files changed
+516/−0 lines, 8 filesdep +basedep +containersdep +gongju-text
Dependencies added: base, containers, gongju-text, hspec, mono-traversable, text, vector
Files
- CHANGELOG.md +25/−0
- LICENSE +189/−0
- README.md +56/−0
- gongju-text.cabal +67/−0
- src/Gongju/Text/Layout.hs +52/−0
- src/Gongju/Text/NonEmpty.hs +45/−0
- src/Gongju/Text/Stanza.hs +43/−0
- test/Main.hs +39/−0
+ CHANGELOG.md view
@@ -0,0 +1,25 @@+# Changelog for `gongju-text`++All notable changes to the `gongju-text` package will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),+and this project adheres to the Haskell Package Versioning Policy (PVP).++## [0.1.0.0] — 2026-10-24++### Added+- **`Gongju.Text.NonEmpty`**: Non-empty text refinements and safe splitting utilities using `mono-traversable`'s `NonNull`:+ - `toText`: Convert non-null text to strict text.+ - `fromText`: Safe constructor returning `Maybe (NonNull Text)`.+ - `wordsNE`: Split text into non-empty words as `Maybe (NonEmpty Text)`.+ - `linesNE`: Split text into non-empty lines as `Maybe (NonEmpty Text)`.+- **`Gongju.Text.Stanza`**: Stanza splitting and parsing over polymorphic containers:+ - `Stanza`: Newtype wrapper around polymorphic text containers.+ - `stanzas`: Split strict text into stanzas of non-blank lines as standard lists (`[Stanza []]`).+ - `stanzasSeq`: Split strict text into stanzas collecting lines into `Data.Sequence.Seq`.+- **`Gongju.Text.Layout`**: Lightweight indentation measurement and hierarchical block-nesting analysis:+ - `IndentedLine`: Line representation with measured leading whitespace indentation depth.+ - `Block`: Nested block structure with line content and recursive child sub-blocks.+ - `parseIndentedLines`: Measure space indentation depths across line lists.+ - `parseBlocks`: Build nested hierarchical block trees from indented lines.+- Full test suite covering non-empty splits, stanza chunking, and hierarchical indentation parsing.
+ LICENSE view
@@ -0,0 +1,189 @@+EUROPEAN UNION PUBLIC LICENCE v. 1.2+EUPL © the European Union 2007, 2016++This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the+terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such+use is covered by a right of the copyright holder of the Work).+The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following+notice immediately following the copyright notice for the Work:+ Licensed under the EUPL+or has expressed by any other means his willingness to license under the EUPL.++1.Definitions+In this Licence, the following terms have the following meaning:+— ‘The Licence’:this Licence.+— ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available+as Source Code and also as Executable Code as the case may be.+— ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or+modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work+required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in+the country mentioned in Article 15.+— ‘The Work’:the Original Work or its Derivative Works.+— ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and+modify.+— ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by+a computer as a program.+— ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence.+— ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to+the creation of a Derivative Work.+— ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the+Licence.+— ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating,+transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential+functionalities at the disposal of any other natural or legal person.++2.Scope of the rights granted by the Licence+The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for+the duration of copyright vested in the Original Work:+— use the Work in any circumstance and for all usage,+— reproduce the Work,+— modify the Work, and make Derivative Works based upon the Work,+— communicate to the public, including the right to make available or display the Work or copies thereof to the public+and perform publicly, as the case may be, the Work,+— distribute the Work or copies thereof,+— lend and rent the Work or copies thereof,+— sublicense rights in the Work or copies thereof.+Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the+applicable law permits so.+In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed+by law in order to make effective the licence of the economic rights here above listed.+The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the+extent necessary to make use of the rights granted on the Work under this Licence.++3.Communication of the Source Code+The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as+Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with+each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to+the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to+distribute or communicate the Work.++4.Limitations on copyright+Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the+exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations+thereto.++5.Obligations of the Licensee+The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those+obligations are the following:++Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to+the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the+Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work+to carry prominent notices stating that the Work has been modified and the date of modification.++Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this+Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless+the Original Work is expressly distributed only under this version of the Licence — for example by communicating+‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the+Work or Derivative Work that alter or restrict the terms of the Licence.++Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both+the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done+under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed+in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with+his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail.++Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide+a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available+for as long as the Licensee continues to distribute or communicate the Work.+Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names+of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and+reproducing the content of the copyright notice.++6.Chain of Authorship+The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or+licensed to him/her and that he/she has the power and authority to grant the Licence.+Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or+licensed to him/her and that he/she has the power and authority to grant the Licence.+Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions+to the Work, under the terms of this Licence.++7.Disclaimer of Warranty+The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work+and may therefore contain defects or ‘bugs’ inherent to this type of development.+For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind+concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or+errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this+Licence.+This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work.++8.Disclaimer of Liability+Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be+liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the+Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss+of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However,+the Licensor will be liable under statutory product liability laws as far such laws apply to the Work.++9.Additional agreements+While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services+consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole+responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify,+defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by+the fact You have accepted any warranty or additional liability.++10.Acceptance of the Licence+The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window+displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of+applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms+and conditions.+Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You+by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution+or Communication by You of the Work or copies thereof.++11.Information to the public+In case of any Distribution or Communication of the Work by means of electronic communication by You (for example,+by offering to download the Work from a remote location) the distribution channel or media (for example, a website)+must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence+and the way it may be accessible, concluded, stored and reproduced by the Licensee.++12.Termination of the Licence+The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms+of the Licence.+Such a termination will not terminate the licences of any person who has received the Work from the Licensee under+the Licence, provided such persons remain in full compliance with the Licence.++13.Miscellaneous+Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the+Work.+If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or+enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid+and enforceable.+The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of+the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence.+New versions of the Licence will be published with a unique version number.+All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take+advantage of the linguistic version of their choice.++14.Jurisdiction+Without prejudice to specific agreement between parties,+— any litigation resulting from the interpretation of this License, arising between the European Union institutions,+bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice+of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union,+— any litigation arising between other parties and resulting from the interpretation of this License, will be subject to+the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business.++15.Applicable Law+Without prejudice to specific agreement between parties,+— this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat,+resides or has his registered office,+— this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside+a European Union Member State.++Appendix++‘Compatible Licences’ according to Article 5 EUPL are:+— GNU General Public License (GPL) v. 2, v. 3+— GNU Affero General Public License (AGPL) v. 3+— Open Software License (OSL) v. 2.1, v. 3.0+— Eclipse Public License (EPL) v. 1.0+— CeCILL v. 2.0, v. 2.1+— Mozilla Public Licence (MPL) v. 2+— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3+— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software+— European Union Public Licence (EUPL) v. 1.1, v. 1.2+— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+).++The European Commission may update this Appendix to later versions of the above licences without producing+a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the+covered Source Code from exclusive appropriation.+All other changes or additions to this Appendix require the production of a new EUPL version.
+ README.md view
@@ -0,0 +1,56 @@+# gongju-text++**Structured text utilities, non-empty text collections, and layout parsing for Haskell.**++`gongju-text` provides lightweight, robust text processing utilities leveraging non-empty refinements, polymorphic container targets, and layout-aware block and stanza parsing.++---++## Features++1. **Non-Empty Text Refinements (`Gongju.Text.NonEmpty`):**+ - Safe construction and unwrap via `NonNull Text` (`fromText`, `toText`).+ - Non-empty word splitting (`wordsNE :: Text -> Maybe (NonEmpty Text)`).+ - Non-empty line splitting (`linesNE :: Text -> Maybe (NonEmpty Text)`).++2. **Polymorphic Stanza Chunking (`Gongju.Text.Stanza`):**+ - Stanza grouping of lines separated by one or more blank or whitespace lines.+ - Container-polymorphic wrapper `Stanza container`.+ - List-based (`stanzas :: Text -> [Stanza []]`) and sequence-based (`stanzasSeq :: Text -> Seq (Stanza Seq)`) chunking.++3. **Indentation and Block Parsing (`Gongju.Text.Layout`):**+ - Space-indentation measuring (`parseIndentedLines :: [Text] -> [IndentedLine]`).+ - Hierarchical indentation block tree construction (`parseBlocks :: [Text] -> [Block]`).+ - Zero-dependency layout analysis without full Markdown or YAML parser overhead.++---++## Quick Example++```haskell+{-# LANGUAGE OverloadedStrings #-}++import qualified Data.Text as T+import Gongju.Text.NonEmpty (wordsNE)+import Gongju.Text.Stanza (stanzas, unStanza)+import Gongju.Text.Layout (parseBlocks, Block(..))++main :: IO ()+main = do+ -- 1. Non-empty word splitting+ print (wordsNE "hello world")++ -- 2. Stanza grouping+ let doc = "First stanza line 1\nLine 2\n\nSecond stanza line 1\nLine 2"+ mapM_ (print . unStanza) (stanzas doc)++ -- 3. Indented block hierarchy+ let lines' = ["root", " child 1", " child 2", " grandchild"]+ print (parseBlocks lines')+```++---++## License++Licensed under the European Union Public Licence (EUPL-1.2) or Apache License 2.0.
+ gongju-text.cabal view
@@ -0,0 +1,67 @@+cabal-version: 3.0+name: gongju-text+version: 0.1.0.0+synopsis: Structured text utilities, non-empty text collections, and layout parsing+description:+ Gongju-Text provides robust text processing utilities leveraging non-empty+ refinements, polymorphic container targets, and layout-aware block/stanza parsing.+license: EUPL-1.2 OR Apache-2.0+license-files: LICENSE+author: Nadia Yvette Chambers+maintainer: nadia.yvette.chambers@ik.me+homepage: https://github.com/NadiaYvette/gongju+bug-reports: https://github.com/NadiaYvette/gongju/issues+category: Text, Parsing, Utilities+build-type: Simple+extra-doc-files:+ README.md+ , CHANGELOG.md+ , LICENSE++tested-with:+ GHC == 9.6.7+ , GHC == 9.8.4+ , GHC == 9.10.3+ , GHC == 9.12.4+ , GHC == 9.14.1++source-repository head+ type: git+ location: https://github.com/NadiaYvette/gongju.git+ subdir: packages/gongju-text++common commons+ default-language: Haskell2010+ default-extensions:+ DerivingStrategies+ OverloadedStrings+ ScopedTypeVariables+ StandaloneDeriving+ UndecidableInstances+ ghc-options: -Wall -Wcompat -Wredundant-constraints++library+ import: commons+ hs-source-dirs: src+ exposed-modules:+ Gongju.Text.NonEmpty+ Gongju.Text.Stanza+ Gongju.Text.Layout+ build-depends:+ base >= 4.18 && < 5+ , text >= 2 && < 3+ , mono-traversable >= 1 && < 2+ , containers >= 0.6 && < 1+ , vector >= 0.12 && < 1++test-suite gongju-text-test+ import: commons+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ build-depends:+ base >= 4.18 && < 5+ , text >= 2 && < 3+ , gongju-text+ , hspec >= 2.11 && < 3+ , containers >= 0.6 && < 1
+ src/Gongju/Text/Layout.hs view
@@ -0,0 +1,52 @@+{- |+Module : Gongju.Text.Layout+Description : Lightweight layout, indentation, and block-nesting analysis+Copyright : (c) Nadia Yvette Chambers, 2026+License : EUPL-1.2 OR Apache-2.0++Provides indentation measuring and hierarchical block structure resolution+without relying on full specification codecs like Markdown.+-}+module Gongju.Text.Layout+ ( IndentedLine (..)+ , Block (..)+ , parseIndentedLines+ , parseBlocks+ ) where++import qualified Data.Text as T++-- | A line with measured leading whitespace indentation depth.+data IndentedLine = IndentedLine+ { indentDepth :: !Int+ , lineContent :: !T.Text+ } deriving stock (Show, Eq)++-- | A nested block of content based on indentation level.+data Block = Block+ { blockIndent :: !Int+ , blockLines :: ![T.Text]+ , subBlocks :: ![Block]+ } deriving stock (Show, Eq)++-- | Convert raw lines into 't:IndentedLine' records counting space depth.+parseIndentedLines :: [T.Text] -> [IndentedLine]+parseIndentedLines = map parseLine+ where+ parseLine t =+ let (spaces, rest) = T.span (== ' ') t+ in IndentedLine (T.length spaces) rest++-- | Group indented lines into nested hierarchical blocks.+parseBlocks :: [T.Text] -> [Block]+parseBlocks lines' = buildBlocks (parseIndentedLines $ filter (not . T.null . T.strip) lines')++buildBlocks :: [IndentedLine] -> [Block]+buildBlocks [] = []+buildBlocks (IndentedLine d content : rest) =+ let (sameOrDeeper, shallower) = span (\l -> indentDepth l >= d) rest+ (direct, deeper) = span (\l -> indentDepth l == d) sameOrDeeper+ currLines = content : map lineContent direct+ nested = buildBlocks deeper+ currBlock = Block d currLines nested+ in currBlock : buildBlocks shallower
+ src/Gongju/Text/NonEmpty.hs view
@@ -0,0 +1,45 @@+{- |+Module : Gongju.Text.NonEmpty+Description : Non-empty text refinements and safe splitting utilities+Copyright : (c) Nadia Yvette Chambers, 2026+License : EUPL-1.2 OR Apache-2.0++Provides safe wrappers and collection operations over non-empty text values+utilizing 'mono-traversable' 'NonNull' types, ensuring operations like word+splitting and regex-style matching yield meaningful non-empty components.+-}+module Gongju.Text.NonEmpty+ ( NonNull+ , toText+ , fromText+ , wordsNE+ , linesNE+ ) where++import qualified Data.Text as T+import Data.List.NonEmpty (NonEmpty (..))+import Data.NonNull (NonNull, fromNullable, toNullable)++-- | Convert a NonNull Text back to a strict Text.+toText :: NonNull T.Text -> T.Text+toText = toNullable++-- | Attempt to create a NonNull Text from a strict Text.+fromText :: T.Text -> Maybe (NonNull T.Text)+fromText = fromNullable++-- | Split text into non-empty words as a NonEmpty collection.+wordsNE :: T.Text -> Maybe (NonEmpty T.Text)+wordsNE t =+ let ws = filter (not . T.null) (T.words t)+ in case ws of+ [] -> Nothing+ (w:ws') -> Just (w :| ws')++-- | Split text into non-empty lines as a NonEmpty collection.+linesNE :: T.Text -> Maybe (NonEmpty T.Text)+linesNE t =+ let ls = filter (not . T.null) (T.lines t)+ in case ls of+ [] -> Nothing+ (l:ls') -> Just (l :| ls')
+ src/Gongju/Text/Stanza.hs view
@@ -0,0 +1,43 @@+{- |+Module : Gongju.Text.Stanza+Description : Stanza splitting and parsing over polymorphic containers+Copyright : (c) Nadia Yvette Chambers, 2026+License : EUPL-1.2 OR Apache-2.0++Handles grouping of lines into stanzas separated by one or more blank lines,+with support for polymorphic container targets (Lists, Sequences, Vectors).+-}+module Gongju.Text.Stanza+ ( Stanza (..)+ , stanzas+ , stanzasSeq+ ) where++import qualified Data.Text as T+import qualified Data.Sequence as Seq+import Data.Sequence (Seq)++-- | A stanza represents a block of non-blank lines.+newtype Stanza container = Stanza { unStanza :: container T.Text }++deriving instance Show (container T.Text) => Show (Stanza container)+deriving instance Eq (container T.Text) => Eq (Stanza container)++-- | Split text into stanzas separated by blank lines, returning standard lists.+stanzas :: T.Text -> [Stanza []]+stanzas = fmap (Stanza . filter (not . T.null)) . splitStanzas . T.lines+ where+ splitStanzas [] = []+ splitStanzas ls =+ let (block, rest) = break isBlank ls+ in case block of+ [] -> splitStanzas (dropWhile isBlank rest)+ b -> b : splitStanzas (dropWhile isBlank rest)++ isBlank = T.null . T.strip++-- | Split text into stanzas, collecting lines into 'Seq.Seq'.+stanzasSeq :: T.Text -> Seq (Stanza Seq)+stanzasSeq = Seq.fromList . fmap toSeqStanza . stanzas+ where+ toSeqStanza (Stanza xs) = Stanza (Seq.fromList xs)
+ test/Main.hs view
@@ -0,0 +1,39 @@+module Main (main) where++import Test.Hspec+import qualified Data.Text as T+import qualified Data.Sequence as Seq+import Data.List.NonEmpty (NonEmpty (..))+import Gongju.Text.NonEmpty+import Gongju.Text.Stanza+import Gongju.Text.Layout++main :: IO ()+main = hspec $ do+ describe "Gongju.Text.NonEmpty" $ do+ it "fromText returns Nothing for empty text" $+ fromText T.empty `shouldBe` Nothing++ it "wordsNE splits words into NonEmpty" $+ wordsNE "hello world" `shouldBe` Just (T.pack "hello" :| [T.pack "world"])++ describe "Gongju.Text.Stanza" $ do+ it "parses stanzas separated by blank lines" $ do+ let sample = "Line 1\nLine 2\n\nLine 3\n"+ res = stanzas sample+ res `shouldBe` [Stanza [T.pack "Line 1", T.pack "Line 2"], Stanza [T.pack "Line 3"]]++ it "parses stanzas into Seq containers" $ do+ let sample = "A\n\nB\n"+ res = stanzasSeq sample+ res `shouldBe` Seq.fromList [Stanza (Seq.singleton (T.pack "A")), Stanza (Seq.singleton (T.pack "B"))]++ describe "Gongju.Text.Layout" $ do+ it "computes indentation depths" $ do+ let raw = [T.pack " indented"]+ parseIndentedLines raw `shouldBe` [IndentedLine 2 (T.pack "indented")]++ it "constructs nested blocks" $ do+ let raw = [T.pack "root", T.pack " child"]+ res = parseBlocks raw+ res `shouldBe` [Block 0 [T.pack "root"] [Block 2 [T.pack "child"] []]]