{- | The "TDisplay" is used to print out information that is ready to display. The process of determining the information to display is separate from the act of displaying; display data can be thought of as a static list of display items presented to a display engine to act upon. -} module TDisplay ( -- * Background -- $background -- * Typed Display Considerations -- $considerations -- * Output Object Classification -- $classification -- * Details -- ** Types TD_TypeTag, TD_FamilyTag, TDTag(..), TDOutput(..), TDOutputsS, -- ** Classes TDShow(..), TDisplay(..), -- ** Functions tdshows, tdshowsLn, tdshowsSep, tdshowsEmpty, -- *** Combining outputs (<+:>), (<++>), (<|:>), (<|+>), -- *** Filtering tdFilter, find_ingroup, find_subgroup, seal_group, isTDGroup, getTDTag, -- ** Tags -- *** Basic\/Primary tdTag_NULL, tdTag_EOL, tdTag_sep, tdTag_MiscText, tdTag_MiscErr, tdTag_MiscError, tdTag_MiscDebug, tdTag_label, tdTag_units, -- *** Basic Grouping tdTag_MiscGroup, tdTag_GroupStartDelim, tdTag_GroupEndDelim, -- *** Versioning -- | Versioning tags are fundamental tags that allow -- clients to determine specific versioning information -- for any related component and take programmatic -- action based on those versions as well as displaying -- the version information. tdTag_VersionNum, tdTag_VersionName, -- ** Standard TDOutput elements tdEOL, tdSep, ) where import Data.Either import Data.List (isPrefixOf) {- $background The TDisplay environment differs from a classical environment in that the latter typically only contains one or two output streams (e.g. stdout and stderr); the "TDisplay" roughly corresponds to the /View/ element of a @Model\/View\/Controller@ (MVC) paradigm. The purpose of the "TDisplay" is to allow an object to determine what the content of its user-visible output should be, but to allow the subsequent components (and ultimately the displaying component) to determine how to manipulate and ultimately present those outputs based on the individual object. There are two aspects to displaying any particular object: the content and the attributes. The content is static and essentially the same content that would be returned by a /Show/ operation (this concept is refined slightly below). The main job of the "TDisplay" engine is to determine the attributes of displaying that content. These attributes may vary by the type of display, but sample attributes might include: * display or suppress a specific item * color * font * location The classical @stdout@ environment is sufficient for simple tasks, but any component wishing to utilize the output of any previous component must resort to performing analysis of the undifferentiated output of the previous task (e.g. @expect@ scripts). This re-synthesis of output information is error-prone and necessarily restricted by the lower bandwidth and lack of differentiation of a simple byte-stream of output. The "TDisplay" mechanism attempts to maintain object information along with that object's (potential) output up until the final point that it is displayed to the actual user. This has several advantages: * Interim or serial tasks can therefore use this object information to more precisely analyze and operate upon the output of previous components. * The manner and selection of final output can be modified by the final output component without requiring extensive control flags and customization throughout the \"Model\" and \"Controller\" code. * The method of displaying the output can be modified independently of the determination of the output. Properly specified, the output should be able to be utilized in a text-based console environment, a GUI environment, and a Web-UI environment without change in the manner in which the output is determined\/generated. -} {- $considerations In order to display (present) information then there are three primary considerations: 1. Individual \"/instances/\" of items to display 2. The manner in which the Display object chooses to display an item 3. How the Display object recognizes the item to determine the manner in which it is displayed. The first consideration is relatively easy. When an particular set of code determines a \"result\" that might need to be displayed to the user, that result is bound to the growing list of "TDOutput" objects. This might commonly be done by using a Writer monad (e.g. @Writer [TDOutput] a@), but the use of the "Writer" monad is not required. Ultimately, this list of "TDOutput" objects is handed to a display object for presentation. The second consideration is also relatively easy. For each "TDOutput" object, the "TDisplay" object can consult an ordered list to find a match for the "TDOutput" object and thus obtain corresponding information about how to display the object. Ultimately the "TDisplay" will perform a show on the object to get the actual data to be displayed. -} {- $classification The final consideration is the most difficult to manage. At the most granular level, an object should be identifiable by a unique \"/type/\". This allows the "TDisplay" object to specifically select that type and display it with attributes specific to that type. However, it is also desireable to indicate one or more \"/family/\" types for the object. This allows the "TDisplay" to make generalizations and removes the requirement that the "TDisplay" have a complete enumeration of specific 'TDisplay.TDOutput' types. In addition, avoidance of any sort of global namespace for specific "TDOutput" types is desireable. It maybe be common for a particular application to utilize several different subcomponents, where each subcomponent has different "TDOutput" information and may be used in other applications or different arrangements. Requiring a global namespace would impose an onerous registration and maintenance chore upon the programmer. At this point, a digression into the consideration of output object granularity is useful. To wit: a particular object may be a composite of sub-objects. As a specific example, a list of objects will need to consult the individual objects for display information, but the list itself also has display elements and a possible effect on the display of its component objects (this is the refinement to the simple Show of content discussed above). This decomposition of output elements should be performed as reasonable, including member elements of composite data structures, container elements, etc. At some point however, \"atomic\" output elements should be assumed to avoid /reducto ad absurdum/. The developer should exercise good judgement in deciding where sub-element decomposition should stop. Bringing the two considerations together (object granularity and specific TDOutput type classification) yields a functional solution for both elements when the granularity aspect is recognized as extending in the upward direction to include the component heirarchy within which the output object is generated. When organized in this manner, each TDOutput \"/type/\" may be assigned on the basis of a unique tag name that is relative to (and unique within) its location in the heirarchy of functional components. Since the TDOutput \"/type/\" is heirarchical, uniqueness is achieved by considering the full path; type tags may then be assigned as somewhat arbitrary strings by the programmer requiring only uniqueness within the current code group. In addition to the \"/type/\" tag, a number of \"/family/\" tags can also be associated with the 'TDOutput'. The \"/family/\" tags are not heirarchical, and should be unique, but the size of this namespace should be reasonably modest since these represent generalizations of output object types for the purposes of broader or \"/fallback/\" output management. * Note: Both \"/type tags/\" and \"/family tags/\" are represented by String objects; it is assume that Haskell optimization, along with any underlying intermediate language optimization (e.g. C) will be sufficiently capable of generating tag comparisons at compile time rather than suffering excessive runtime string comparison penalties. -} -- TD_Id -> TD_TypeTag type TD_TypeTag = String -- ^ unique for each element output type TD_FamilyTag = String -- ^ family tag specification data TDTag = TDTag TD_TypeTag [TD_FamilyTag] deriving (Eq,Show) {- | The registry contains a list of Id's, and a list of possible attributes associated with each Id. -} --type TD_Registry = [(TD_Id, [TD_Attr])] {- | The TDShow class is very similar to the Show class, except that each element shown has an associated TD_Id. The TDShow methods take an extra argument to identify the type and family of the object being displayed; the output of TDShow is TDOutput. Note that an object may need to be decomposed, and it should recursively invoke tdshow on its sub-members, placing the results in a TDGroup. An instance declaring itself to be a member of this class can be displayed using tdshow techniques. The instance must minimally define either: * tdshow * tdshowsPrec -} data TDOutput = TDString TDTag String | TDAtom TDTag | TDHsepGroup TDTag OutputMaybeS | TDVsepGroup TDTag OutputMaybeS | TDGroup TDTag OutputMaybeS deriving (Show, Eq) type TDOutputsS = [TDOutput] -> [TDOutput] type OutputMaybeS = Either [TDOutput] TDOutputsS isTDGroup :: TDOutput -> Bool isTDGroup (TDHsepGroup _ _) = True isTDGroup (TDVsepGroup _ _) = True isTDGroup (TDGroup _ _) = True isTDGroup _ = False getTDTag :: TDOutput -> TDTag getTDTag (TDString t _) = t getTDTag (TDAtom t) = t getTDTag (TDHsepGroup t _) = t getTDTag (TDVsepGroup t _) = t getTDTag (TDGroup t _) = t -- satisfy the basic Show and Eq need; TDisplayZZZ should do better. instance Show TDOutputsS where show o = showString "[TDOutput] -> " $ (shows $ o []) " ++ [TDOutput]" instance Eq TDOutputsS where _ == _ = False {- | Primary class TDShow; objects that are members have the ability to directly guide their tagged output display. -} class (Show a) => TDShow a where tdshow :: TDTag -> a -> [TDOutput] tdshowsPrec :: Integer -> TDTag -> a -> TDOutputsS tdshow i a = (tdshowsPrec 0 i a) tdNULL tdshowsPrec d i a | d > 10 = (++) [ TDGroup tdTag_MiscGroup $ Left $ tdshows tdTag_GroupStartDelim "(" $ tdshows i a $ tdshow tdTag_GroupEndDelim ")" ] | otherwise = (++) (tdshow i a) tdshows :: (TDShow a) => TDTag -> a -> TDOutputsS tdshows i a = tdshowsPrec 0 i a tdshowsLn :: (TDShow a) => TDTag -> a -> TDOutputsS tdshowsLn i a = tdshows i a . (++) tdEOL tdshowsSep :: (TDShow a) => TDTag -> a -> TDOutputsS tdshowsSep i a = tdshows i a . (++) tdSep --kwq: sep should be automatic between elements of a group? tdshowsEmpty :: TDOutputsS tdshowsEmpty = (++) [] infixr 0 <+:> (<+:>) :: TDTag -> [TDOutput] -> TDOutputsS i <+:> g = (++) [TDHsepGroup i (Left g)] infixr 0 <++> (<++>) :: TDTag -> TDOutputsS -> TDOutputsS i <++> g = (++) [TDHsepGroup i (Right g)] infixr 1 <|:> (<|:>) :: TDTag -> [TDOutput] -> TDOutputsS i <|:> g = (++) [TDVsepGroup i (Left g)] infixr 1 <|+> (<|+>) :: TDTag -> TDOutputsS -> TDOutputsS i <|+> g = (++) [TDVsepGroup i (Right g)] {- | The TDisplay class is the class which actually displays the TDOutput class elements to the current output device. The simplest TDisplay is essentially: @ putStr . snd @ It is common that TDisplay will filter out specific TD_Id elements, or display various TD_Id elements with different colors or other functionality. -} class TDisplay a where {- | render is called to display a list of TDOutput instances on the current display. The render may need to be called multiple times to fully render the instances; the renderComplete method can be used to determine when rendering has fully completed. -} render :: [TDOutput] -> a -> a {- | renderComplete is called to determine if all currently presented output has been displayed. This may have side effects like causing flushing of output or refresh operations. -} renderComplete :: a -> Bool ---------------------------------------------------------------------- -- Basic/primary TDOutput tags -- tdId_EOT :: TD_Id -- tdId_EOT = 0 -- tdEOT :: [TDOutput] -- tdEOT = [TDString tdId_EOT ""] --kwq: make this a primary TDOutput type tdTag_NULL :: TDTag -- ^ a filler\/placeholder; does not have output content tdTag_NULL = TDTag "null" [] tdNULL :: [TDOutput] tdNULL = [TDString tdTag_NULL ""] tdTag_EOL :: TDTag tdTag_EOL = TDTag "EOL" ["eol", "separator", "whitespace"] tdEOL :: [TDOutput] tdEOL = [TDAtom tdTag_EOL] tdTag_sep :: TDTag tdTag_sep = TDTag "separator" [ "separator", "whitespace" ] tdSep :: [TDOutput] tdSep = [TDAtom tdTag_sep] tdTag_MiscText :: TDTag tdTag_MiscText = TDTag "output" ["text"] tdTag_MiscErr :: TDTag tdTag_MiscErr = TDTag "error" ["text", "error"] tdTag_MiscError :: TDTag tdTag_MiscError = tdTag_MiscErr tdTag_MiscDebug :: TDTag tdTag_MiscDebug = TDTag "debug" ["text", "debug"] tdTag_MiscGroup :: TDTag tdTag_MiscGroup = TDTag "group" ["group"] tdTag_GroupStartDelim :: TDTag tdTag_GroupStartDelim = TDTag "groupstartdelim" ["text", "delimiter", "start delimiter"] tdTag_GroupEndDelim :: TDTag tdTag_GroupEndDelim = TDTag "groupenddelim" ["text", "delimiter", "end delimiter"] tdTag_label :: TDTag tdTag_label = TDTag "label" [ "text", "label" ] tdTag_units :: TDTag tdTag_units = TDTag "units" [ "text", "units" ] tdTag_VersionNum :: TDTag tdTag_VersionNum = TDTag "version num" [ "text", "version" ] tdTag_VersionName :: TDTag tdTag_VersionName = TDTag "version name" [ "text", "version" ] ---------------------------------------------------------------------- -- Fundamental Type TDShow instances instance TDShow Char where tdshow i a = [TDString i [a]] instance TDShow [Char] where tdshow i a = [TDString i a] instance TDShow Int where tdshow i a = [TDString i (show a)] -- kwq: should this havea a Number TDTag? ---------------------------------------------------------------------- -- Prelude Data.X TDShow instances instance (Show a) => TDShow (Maybe a) where tdshow _i Nothing = [TDString tdTag_Nothing "Nothing"] tdshow i (Just m) = [TDGroup tdTag_Just $ Left $ [TDString i (show m)]] tdTag_Nothing :: TDTag tdTag_Nothing = TDTag "Nothing" ["maybe", "group"] tdTag_Just :: TDTag tdTag_Just = TDTag "Just" ["maybe", "group"] -- kwqkwq: TDShow Int: should specify Int TDTag? Should be base method for fundamental types that supplies the TDTag accordingly? What about tdshow i Nothing: loses i...?! ---------------------------------------------------------------------- -- Filtering operations for [TDOutput] and TDOutputsS tdFilter :: [(TDOutput -> Bool)] -> TDOutputsS -> [TDOutput] -- ^ filters, input stream, output sequence tdFilter fs s = let subfilter (TDHsepGroup t g) = TDHsepGroup t $ filter_group g subfilter (TDVsepGroup t g) = TDVsepGroup t $ filter_group g subfilter (TDGroup t g) = TDGroup t $ filter_group g subfilter x = x filter_group = Left . tdFilter fs . either const id in map subfilter $ foldr filter (s []) fs find_ingroup :: (TDOutput -> Bool) -> TDOutput -> [TDOutput] find_ingroup p (TDHsepGroup _ g) = find_ingroup' p g find_ingroup p (TDVsepGroup _ g) = find_ingroup' p g find_ingroup p (TDGroup _ g) = find_ingroup' p g find_ingroup _ _ = [] find_ingroup' :: (TDOutput -> Bool) -> OutputMaybeS -> [TDOutput] find_ingroup' p = filter p . seal_group find_subgroup :: TDTag -> TDOutput -> [TDOutput] find_subgroup t (TDHsepGroup _ g) = find_subgroup' t g find_subgroup t (TDVsepGroup _ g) = find_subgroup' t g find_subgroup t (TDGroup _ g) = find_subgroup' t g find_subgroup _ _ = [] find_subgroup' :: TDTag -> OutputMaybeS -> [TDOutput] find_subgroup' t = filter (\x -> isTDGroup x && getTDTag x == t) . seal_group seal_group :: OutputMaybeS -> [TDOutput] seal_group = either id (\y -> y [])