diff --git a/Action.hs b/Action.hs
--- a/Action.hs
+++ b/Action.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
diff --git a/FileCorrector.hs b/FileCorrector.hs
--- a/FileCorrector.hs
+++ b/FileCorrector.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
@@ -25,7 +25,7 @@
 import qualified Data.Text.Lazy as T
 import qualified Data.Text.Lazy.IO as TextIO
 import Numeric(showOct)
-import System.Directory (getDirectoryContents, removeFile)
+import System.Directory (getDirectoryContents, removeFile, removeDirectory)
 import qualified System.Directory as SD
 import System.Exit(ExitCode(ExitSuccess))
 import System.FilePath (joinPath, normalise, splitFileName, makeRelative, splitDirectories, dropTrailingPathSeparator, takeFileName, combine)
@@ -44,22 +44,25 @@
 data FT = FileT | DirT | SymlinkT
     deriving (Show, Eq)
 
+data WrongFT = FileWT | SymlinkWT | EmptyDirWT | NonEmptyDirWT
+    deriving (Show, Eq)
+
 data AlsoWrongPermissions = PermissionsWrongToo | PermissionsRight
     deriving (Show, Eq)
 
 -- Intermediate Status of files found (all but for permissions checked)
 data IStatus = IMissing -- File/Dir is not there at all
-             | IWrongType FT -- File when should be Dir, etc.
+             | IWrongType WrongFT -- File when should be Dir, etc.
              | IWrongContents (Maybe String) -- Files: Might as well not be there...
-             | IExtraFiles [(FilePath, FT)] -- Full Dirs: some extra files
+             | IExtraFiles [(FilePath, WrongFT)] -- Full Dirs: some extra files
              | ILooksOk
     deriving (Show, Eq)
 
 -- Final Status of files found
 data FStatus = FMissing -- File/Dir is not there at all
-             | FWrongType FT -- File when should be Dir, etc.
+             | FWrongType WrongFT -- File when should be Dir, etc.
              | FWrongContents (Maybe String) -- Files: Might as well not be there...
-             | FExtraFiles [(FilePath, FT)] AlsoWrongPermissions -- Full Dirs: some extra files
+             | FExtraFiles [(FilePath, WrongFT)] AlsoWrongPermissions -- Full Dirs: some extra files
              | FWrongPermissions -- Content ok, permissions need correcting
              | FPerfect
     deriving (Show, Eq)
@@ -185,7 +188,7 @@
     wrong <- permissionsAreWrong kind path perm
     return $ if wrong then FWrongPermissions else FPerfect
 checkPermissions _ _ _ IMissing = return FMissing
-checkPermissions _ _ _ (IWrongType ft) = return $ FWrongType ft
+checkPermissions _ _ _ (IWrongType wft) = return $ FWrongType wft
 checkPermissions _ _ _ (IWrongContents ms) = return $ FWrongContents ms
 
 permissionsAreWrong :: PathType -> FilePath -> Permissions -> IO Bool
@@ -215,22 +218,26 @@
         then do
             fs <- getSymbolicLinkStatus path
             if isSymbolicLink fs
-                then return $ IWrongType SymlinkT
+                then return $ IWrongType SymlinkWT
                 else if isDirectory fs
                     then
                         case t of
                             Full -> checkContents path paths
                             _ -> return ILooksOk
-                    else return $ IWrongType FileT
+                    else return $ IWrongType FileWT
         else return IMissing
 
+listDirectory :: FilePath -> IO [FilePath]
+listDirectory fp = do
+    fs <- getDirectoryContents fp
+    return $ filter (\f -> f `notElem` [".", ".."]) fs
+
 -- Return ILooksOk if path contains no files other than path.
 -- Otherwise return ExtraFiles.
 checkContents :: FilePath -> Map.Map FilePath Path -> IO IStatus
 checkContents path paths = do
-    allfiles <- getDirectoryContents path
-    let files = filter (\f -> f `notElem` [".", ".."]) allfiles
-        wanted = Set.map (takeFileName.dropTrailingPathSeparator) (Map.keysSet paths)
+    files <- listDirectory path
+    let wanted = Set.map (takeFileName.dropTrailingPathSeparator) (Map.keysSet paths)
         actual = Set.fromList files
         extras = Set.toList $ Set.difference actual wanted
         extrasWithPath = map (combine path) extras
@@ -240,12 +247,20 @@
             withFTs <- mapM appendFileType extrasWithPath
             return $ IExtraFiles withFTs
 
-appendFileType :: FilePath -> IO (FilePath, FT)
+-- Is the given directory empty? helper during calculating WrongFT
+isDirEmpty :: FilePath -> IO WrongFT
+isDirEmpty path = do
+    ls <- listDirectory path
+    return $ if [] == ls then EmptyDirWT else NonEmptyDirWT
+
+appendFileType :: FilePath -> IO (FilePath, WrongFT)
 appendFileType path = do
     fs <- getSymbolicLinkStatus path
-    let t | isSymbolicLink fs = SymlinkT
-          | isDirectory fs = DirT
-          | otherwise = FileT
+    t <- if isSymbolicLink fs
+            then return SymlinkWT
+            else if isDirectory fs
+                    then isDirEmpty path
+                    else return FileWT
     return (path, t)
 
 calcFileStatus :: CompFun -> T.Text -> FilePath -> IO IStatus
@@ -255,9 +270,9 @@
         then do
             fs <- getSymbolicLinkStatus path
             if isSymbolicLink fs
-                then return $ IWrongType SymlinkT
+                then return $ IWrongType SymlinkWT
                 else if isDirectory fs
-                    then return $ IWrongType DirT
+                    then IWrongType <$> isDirEmpty path
                     else compfun contents path
         else return IMissing
 
@@ -288,8 +303,9 @@
                     return $ if l == dest
                                 then ILooksOk
                                 else IWrongContents (Just $ "Symlink is currently " ++ l)
-                else
-                    return $ IWrongType $ if isDirectory fs then DirT else FileT
+                else if isDirectory fs
+                        then IWrongType <$> isDirEmpty source
+                        else return $ IWrongType FileWT
         else do
             isslink <- catchJust (\ex -> if isDoesNotExistErrorType (ioeGetErrorType ex) then Just () else Nothing)
                              (do
@@ -311,9 +327,11 @@
     if e
         then do
             fs <- getSymbolicLinkStatus path
-            return $ IWrongType $ if isSymbolicLink fs
-                                    then SymlinkT
-                                    else if isDirectory fs then DirT else FileT
+            if isSymbolicLink fs
+                then return $ IWrongType SymlinkWT
+                else if isDirectory fs
+                        then IWrongType <$> isDirEmpty path
+                        else return $ IWrongType FileWT
         else
             return ILooksOk
 
@@ -382,9 +400,10 @@
                     (Just a, Just b) -> "--owner " ++ ownerAsString a ++ " --group " ++ groupAsString b ++ " "
 
 correctAbsent :: FStatus -> FilePath -> [Action]
-correctAbsent (FWrongType SymlinkT) path = [Action ("rm "++path) (removeLink path)]
-correctAbsent (FWrongType FileT) path = [Action ("rm "++path) (removeFile path)]
-correctAbsent (FWrongType DirT) path = [Action ("rm -r "++path) (recursiveRemove SystemFS path)]
+correctAbsent (FWrongType SymlinkWT) path = [Action ("rm "++path) (removeLink path)]
+correctAbsent (FWrongType FileWT) path = [Action ("rm "++path) (removeFile path)]
+correctAbsent (FWrongType EmptyDirWT) path = [Action ("rmdir "++path) (removeDirectory path)]
+correctAbsent (FWrongType NonEmptyDirWT) path = [Action ("rm -r "++path) (recursiveRemove SystemFS path)]
 correctAbsent FMissing _ = [] -- Occurs when parent directories do not exist
 correctAbsent s path = error $ "Unexpected correctAbsent state: " ++ show s ++ " (occurred for path " ++ path ++ ")"
 
@@ -407,7 +426,7 @@
 correctFile (FWrongContents w) c p perm =
     let (mogText, mogIO) = toModeOwnerGroup FileT p perm ""
         c' = appendNewlineIfMissing c
-    in (emptyPermissions, correctAbsent (FWrongType FileT) p
+    in (emptyPermissions, correctAbsent (FWrongType FileWT) p
                           ++ showWrong w
                           ++ [Action ("install "++mogText) (do {TextIO.writeFile p c'; mogIO})])
 correctFile FMissing c p perm =
@@ -419,7 +438,7 @@
 correctSymlink :: FStatus -> FilePath -> FilePath -> Permissions -> (Permissions, [Action])
 correctSymlink (FWrongContents w) fp p perm =
     let (perm', fixmissing) = correctSymlink FMissing fp p perm
-    in (perm', correctAbsent (FWrongType SymlinkT) p ++ showWrong w ++ fixmissing)
+    in (perm', correctAbsent (FWrongType SymlinkWT) p ++ showWrong w ++ fixmissing)
 correctSymlink FMissing fp p perm =
     let (mogText, mogIO) = toModeOwnerGroup FileT p perm ("-s " ++ fp)
     in (emptyPermissions, [Action ("ln " ++ mogText) (do {createSymbolicLink fp p; mogIO})])
diff --git a/Interpret.hs b/Interpret.hs
--- a/Interpret.hs
+++ b/Interpret.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
@@ -26,11 +26,12 @@
 import qualified Data.Text.Lazy as T
 import qualified Data.Text.Lazy.IO as TextIO
 import qualified Data.Traversable as Traversable
-import Numeric(readOct)
+import Numeric(readOct, readDec, readSigned)
 import System.Posix.Types(FileMode)
 import System.Directory (doesFileExist)
 import System.FilePath (combine)
 import System.IO (Handle)
+import Text.Printf(printf)
 
 import FileCorrector (PathType(..), DirType(..), Path, Permissions(..), emptyPermissions, OwnerP(..), GroupP(..))
 import InterpretState
@@ -497,6 +498,7 @@
 expandOne _ _ [CRawString s] = return $ Right $ Expanded s
 expandOne _ al [CString w] = return $ expandStringFromAl al w
 expandOne _ al [CExplicitString w] = return $ expandStringFromAl al w
+expandOne _ _ [CLinn n] = return $ Right $ Expanded $ T.pack $ show n
 expandOne _ al [CRawFile f] =
     case expandStringFromAl al f of
         Right (Expanded s) -> do
@@ -513,6 +515,37 @@
             return $ Right $ EFile s' al
         Right _ -> return $ Left "Only simple expansions supported for filenames"
         Left e  -> return $ Left e
+expandOne _ al [CNumFormat f n] = do
+    f' <- case expandStringFromAl al f of
+        Right (Expanded s) ->
+            return $ T.unpack s
+        Right _ -> do
+            addError "Only simple expansions supported for $numformat format strings"
+            return "10"
+        Left e  -> do
+            addError e
+            return "10"
+    n' <- case expandStringFromAl al n of
+        Right (Expanded s) ->
+            return $ T.unpack s
+        Right _ -> do
+            addError "Only simple expansions supported for $numformat number strings"
+            return "987"
+        Left e  -> do
+            addError e
+            return "987"
+    f'' <- case readSigned readDec f' of
+                    [(x, "")] -> return x :: Interpret Integer
+                    _ -> do
+                        addError $ "Failed to parse $numformat format: " ++ f'
+                        return 10
+    n'' <- case readSigned readDec n' of
+                    [(x, "")] -> return x :: Interpret Integer
+                    _ -> do
+                        addError $ "Failed to parse $numformat number: " ++ n'
+                        return 987
+    let n''' = if n'' >= 0 then n'' else 10^f'' + n''
+    return $ Right $ Expanded $ T.pack $ printf ("%0" ++ show f'' ++ "d") n'''
 expandOne pp al [CFragments (Group g) (Prepend prepend) (Append append) (Separator sep)] = do
     g' <- case expandStringFromAl al g of
         Right (Expanded s) ->
diff --git a/InterpretState.hs b/InterpretState.hs
--- a/InterpretState.hs
+++ b/InterpretState.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
diff --git a/Parser.hs b/Parser.hs
--- a/Parser.hs
+++ b/Parser.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
@@ -41,6 +41,9 @@
 incNewline :: P ()
 incNewline = stUpdate (\(State (n, es)) -> State(n+1, es))
 
+lineNo :: P Int
+lineNo = stQuery (\(State (n, _)) -> n)
+
 addError :: String -> P ()
 addError e = stUpdate (\(State (n, es)) -> State(n, prep n:es))
     where
@@ -70,8 +73,8 @@
                 " with the rest of the line being " ++ show r
         ]
 
-toContent' :: ContentType -> ContentLine -> P [Content]
-toContent' ct cl = case toContent ct cl of
+toContent' :: Int -> ContentType -> ContentLine -> P [Content]
+toContent' lineno ct cl = case toContent lineno ct cl of
     Left e -> addError e >> return []
     Right cs -> return cs
 
@@ -323,9 +326,10 @@
 
 pContent :: P [Content]
 pContent = do
+    lineno <- lineNo
     ct <- pContentType pSpaceButCheckSingleTab
     c <- pManyUntilNewline
-    toContent' ct (Plain c)
+    toContent' lineno ct (Plain c)
 
 pDefine :: P (DefName, Define)
 pDefine = do
@@ -450,13 +454,15 @@
 pInitialArg :: P [Content]
 pInitialArg = oneOf
     [do char ':'
+        lineno <- lineNo
         ct <- pContentType pSpaceButCheckSingleTab
         l <- pManyUntilNewline
-        toContent' ct (Plain l)
+        toContent' lineno ct (Plain l)
     ,do char '>'
+        lineno <- lineNo
         ct <- pContentType pSpaceButCheckOthers
         l <- many1Satisfy (not . isSpace)
-        toContent' ct (Plain l)
+        toContent' lineno ct (Plain l)
     ]
 
 pArgMore :: P [Content]
@@ -466,13 +472,14 @@
 pArgCont = do
     pJustNewline
     pIndent3AndMaybeComments
+    lineno <- lineNo
     t <- oneOf
         [do {char '+'; return PrependNewline}
         ,do {char '\\'; return Plain}
         ] `adjustError` "was expecting '+' or '\\'"
     ct <- pContentType pSpaceButCheckSingleTab
     l <- pManyUntilNewline
-    toContent' ct (t l)
+    toContent' lineno ct (t l)
 
 -- Parse nothing if at the end of the line already
 pNothingIfAtEndOfLine :: P T.Text
diff --git a/ParserTypes.hs b/ParserTypes.hs
--- a/ParserTypes.hs
+++ b/ParserTypes.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
@@ -131,13 +131,13 @@
 
 data ContentType = CTSpace | CTDollar
 
-toContent :: ContentType -> ContentLine -> Either String [Content]
-toContent ct (PrependNewline s) =
-    case toContent ct (Plain s) of
+toContent :: Int -> ContentType -> ContentLine -> Either String [Content]
+toContent lineno ct (PrependNewline s) =
+    case toContent lineno ct (Plain s) of
         Left e -> Left e
         Right cs -> Right $ CNewline : cs
-toContent _ (Plain s) | T.null s = Right []
-toContent ct (Plain s) =
+toContent _ _ (Plain s) | T.null s = Right []
+toContent lineno ct (Plain s) =
     case ct of
         CTSpace -> return [CString s]
         CTDollar -> case T.span isAlpha s of
@@ -146,11 +146,20 @@
             (a, b) | a == T.pack "rawstring" && T.head b == '(' -> checkEnd (\ss -> Right [CRawString ss]) $ T.tail b
             (a, b) | a == T.pack "string"    && T.head b == '(' -> checkEnd (\ss -> Right [CExplicitString ss]) $ T.tail b
             (a, b) | a == T.pack "fragments" && T.head b == '(' -> checkEnd makeFragments $ T.tail b
+            (a, b) | a == T.pack "numformat" && T.head b == '(' -> checkEnd makeNumFormat $ T.tail b
+            (a, b) | a == T.pack "linn"      && T.head b == '(' -> checkEnd makeLinn $ T.tail b
             _ -> Left $ "Expected a valid $COMMAND but got: $" ++ T.unpack s
         where
             checkEnd f ss = if T.last ss == ')'
                             then f $ T.init ss
                             else Left $ "Content missing final ')'? : " ++ T.unpack ss
+            makeNumFormat ss =
+                let fargs = T.split (== ',') ss
+                    [format, number] = fargs
+                in
+                    if length fargs == 2
+                        then Right [CNumFormat format number]
+                        else Left $ "Wrong number of numformat arguments: " ++ T.unpack ss
             makeFragments ss =
                 let fargs = T.split (== ',') ss
                     [g, prepend, append, sep] = fargs
@@ -158,6 +167,8 @@
                     if length fargs == 4
                         then Right [CFragments (Group g) (Prepend prepend) (Append append) (Separator sep)]
                         else Left $ "Wrong number of fragments arguments: " ++ T.unpack ss
+            makeLinn ss | T.null ss = Right [CLinn lineno]
+            makeLinn ss = Left $ "$linn expects 0 arguments, but received: " ++ T.unpack ss
 
 data Content = CString T.Text -- As input
              | CExplicitString T.Text -- As input within explicit string()
@@ -166,6 +177,8 @@
              | CNewline -- A newline
              | CFile T.Text
              | CFragments Group Prepend Append Separator
+             | CNumFormat T.Text T.Text
+             | CLinn Int
              | CRawFile T.Text
     deriving (Show, Eq)
 
diff --git a/PathToMCM.hs b/PathToMCM.hs
--- a/PathToMCM.hs
+++ b/PathToMCM.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
diff --git a/README.txt b/README.txt
--- a/README.txt
+++ b/README.txt
@@ -1,5 +1,5 @@
 MCM - Machine Configuration Manager; manages the contents of files and directories
-Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 http://interfaces.org.uk/mcm
 
 Written for Debian and friends (though as yet only really tested on Debian and
diff --git a/VarsParser.hs b/VarsParser.hs
--- a/VarsParser.hs
+++ b/VarsParser.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
diff --git a/changelog b/changelog
--- a/changelog
+++ b/changelog
@@ -1,3 +1,14 @@
+[0.6.8.1] 2018-10-25
+    - fix typo and improve description formatting
+[0.6.8.0] 2018-10-14
+    - implement $linn
+[0.6.7.1] 2018-10-13
+    - fix $numformat-wrong-answer-for-0 boundary bug
+[0.6.7.0] 2018-10-13
+    - basic $numformat implementation
+[0.6.6.0] 2017-08-29
+    - distinguish between non/empty directories in MCM's displayed output
+    - make it easier still to run the tests on preexisting binaries 
 [0.6.5.0] 2017-01-09
     - enable mcmtags to generate qualified tags (e.g. Admin.Packages.add)
     - enhance mcm.vim to make use of qualified tags
diff --git a/commands2html.1 b/commands2html.1
--- a/commands2html.1
+++ b/commands2html.1
@@ -56,6 +56,6 @@
 .SH REPORTING BUGS
 Please email the author at the above address, preferably with sufficient information to reproduce the bug.
 .SH COPYRIGHT
-Copyright (c) 2015-2017 Anthony Doggett
+Copyright (c) 2015-2018 Anthony Doggett
 .PP
 <http://interfaces.org.uk/mcm>
diff --git a/commands2html.hs b/commands2html.hs
--- a/commands2html.hs
+++ b/commands2html.hs
@@ -1,6 +1,6 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
diff --git a/copyright b/copyright
--- a/copyright
+++ b/copyright
@@ -1,5 +1,5 @@
 MCM - Machine Configuration Manager; manages the contents of files and directories
-Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 
 Licence:
     This program is free software: you can redistribute it and/or modify
diff --git a/mcm.1 b/mcm.1
--- a/mcm.1
+++ b/mcm.1
@@ -133,7 +133,7 @@
 .SH REPORTING BUGS
 Please email the author at the above address, preferably with sufficient information to reproduce the bug.
 .SH COPYRIGHT
-Copyright (c) 2014-2017 Anthony Doggett
+Copyright (c) 2014-2018 Anthony Doggett
 .PP
 <http://interfaces.org.uk/mcm>
 .SH SEE ALSO
diff --git a/mcm.cabal b/mcm.cabal
--- a/mcm.cabal
+++ b/mcm.cabal
@@ -1,17 +1,19 @@
 name: mcm
-version: 0.6.5.0
-synopsis: Manages the contents of files and directories
-description:  Machine Configuration Manager (MCM) manages the contents of files and
+version: 0.6.8.1
+synopsis: Machine Configuration Manager
+description:
+ Machine Configuration Manager (MCM) manages the contents of files and
  directories.  One or more of those files can be a script, enabling MCM to
  control anything.  Typically MCM is used to manage the configurations of user
  profiles, machines, systems and systems of systems.
- * The declarative language is simple and easy on the eye yet very powerful.
- * MCM is simple, fast and transparent.
+ .
+  * The declarative language is simple and easy on the eye yet very powerful.
+  * MCM is simple, fast and transparent.
 license: GPL-3
 license-file: LICENCE
 author: Anthony Doggett <mcm@interfaces.org.uk>
 maintainer: Anthony Doggett <mcm@interfaces.org.uk>
-copyright: (c) 2013-2017 Anthony Doggett
+copyright: (c) 2013-2018 Anthony Doggett
 homepage: http://interfaces.org.uk/mcm
 category: Language, System, Text
 stability: alpha
diff --git a/mcm.hs b/mcm.hs
--- a/mcm.hs
+++ b/mcm.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
diff --git a/mcm.vim b/mcm.vim
--- a/mcm.vim
+++ b/mcm.vim
@@ -2,7 +2,7 @@
 " Maintainer:  Anthony Doggett (MCM@interfaces.org.uk)
 "
 " MCM - Machine Configuration Manager; manages the contents of files and directories
-" Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+" Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 "
 " Licence:
 "     This program is free software: you can redistribute it and/or modify
@@ -75,9 +75,9 @@
 syn match tagInLine "^\t\t\t[+\\]\($\|[$ ].*\)" contains=tagContinuation,tagContaVar,tagDoubleAt
 syn match tagContinuation "^\t\t\t[+\\]\($\| \).*" contained
 syn match tagContinuation "^\t\t\t[+\\]\zs\$.*" contained contains=tagCommandGroup
-syn match tagCommandGroup "\$\<\(fragments\|rawfile\|rawstring\|file\|string\)(.*)$" contained contains=tagCommand
-syn match tagWordCommandGroup "\$\<\(fragments\|rawfile\|rawstring\|file\|string\)(\S*)" contained contains=tagCommand
-syn match tagCommand "\$\<\(fragments\|rawfile\|rawstring\|file\|string\)(" contained
+syn match tagCommandGroup "\$\<\(linn\|numformat\|fragments\|rawfile\|rawstring\|file\|string\)(.*)$" contained contains=tagCommand
+syn match tagWordCommandGroup "\$\<\(linn\|numformat\|fragments\|rawfile\|rawstring\|file\|string\)(\S*)" contained contains=tagCommand
+syn match tagCommand "\$\<\(linn\|numformat\|fragments\|rawfile\|rawstring\|file\|string\)(" contained
 syn match tagCommand ")$" contained
 syn match tagCommand "," contained
 syn match tagContent "> \zs\S\+" contained
diff --git a/mcm2html.1 b/mcm2html.1
--- a/mcm2html.1
+++ b/mcm2html.1
@@ -56,7 +56,7 @@
 .SH REPORTING BUGS
 Please email the author at the above address, preferably with sufficient information to reproduce the bug.
 .SH COPYRIGHT
-Copyright (c) 2014-2017 Anthony Doggett
+Copyright (c) 2014-2018 Anthony Doggett
 .PP
 <http://interfaces.org.uk/mcm>
 .SH SEE ALSO
diff --git a/mcm2html.hs b/mcm2html.hs
--- a/mcm2html.hs
+++ b/mcm2html.hs
@@ -1,6 +1,6 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
@@ -262,6 +262,8 @@
     toMarkup (CRawString s) = command s "rawstring" toHtml
     toMarkup (CFile f) = command f "file" expandAts
     toMarkup (CRawFile f) = command f "rawfile" expandAts
+    toMarkup (CLinn _) = command T.empty "linn" toHtml
+    toMarkup (CNumFormat f n) = command (T.intercalate (T.pack ",") [f,n]) "numformat" expandAts
     toMarkup (CFragments (Group g) (Prepend p) (Append a) (Separator s)) =
         command (T.intercalate (T.pack ",") [g,p,a,s]) "fragments" expandAts
     toMarkup CNewline = error "Unexpected CNewline"
@@ -329,11 +331,13 @@
 containsWhitespaceOrEmpty :: Content -> Bool
 containsWhitespaceOrEmpty CEmpty = True
 containsWhitespaceOrEmpty CNewline = True
+containsWhitespaceOrEmpty (CLinn _) = False
 containsWhitespaceOrEmpty (CString s) = isJust $ T.find isSpace s
 containsWhitespaceOrEmpty (CExplicitString s) = containsWhitespaceOrEmpty (CString s)
 containsWhitespaceOrEmpty (CRawString s) = isJust $ T.find isSpace s
 containsWhitespaceOrEmpty (CFile f) = isJust $ T.find isSpace f
 containsWhitespaceOrEmpty (CRawFile f) = isJust $ T.find isSpace f
+containsWhitespaceOrEmpty (CNumFormat f n) = any (isJust . T.find isSpace) [f, n]
 containsWhitespaceOrEmpty (CFragments (Group g) (Prepend p) (Append a) (Separator s)) = any (isJust . T.find isSpace) [g, p, a, s]
 
 type ICs = [(Ident, [Content])]
diff --git a/mcmtags.1 b/mcmtags.1
--- a/mcmtags.1
+++ b/mcmtags.1
@@ -35,7 +35,7 @@
 .SH REPORTING BUGS
 Please email the author at the above address, preferably with sufficient information to reproduce the bug.
 .SH COPYRIGHT
-Copyright (c) 2014-2017 Anthony Doggett
+Copyright (c) 2014-2018 Anthony Doggett
 .PP
 <http://interfaces.org.uk/mcm>
 .SH SEE ALSO
diff --git a/mcmtags.hs b/mcmtags.hs
--- a/mcmtags.hs
+++ b/mcmtags.hs
@@ -1,5 +1,5 @@
 -- MCM - Machine Configuration Manager; manages the contents of files and directories
--- Copyright (c) 2013-2017 Anthony Doggett <mcm@interfaces.org.uk>
+-- Copyright (c) 2013-2018 Anthony Doggett <mcm@interfaces.org.uk>
 --
 -- Licence:
 --     This program is free software: you can redistribute it and/or modify
diff --git a/mcmtestfiles.tgz b/mcmtestfiles.tgz
Binary files a/mcmtestfiles.tgz and b/mcmtestfiles.tgz differ
