Modules
-SubLua | -Lua binding to SubCpp - a C++ Subversion library. | +new (options) | +Create a new SVN client to work with. | +
SubLua:Cat (path, options) | +Output the content of specified files or URLs. | +||
SubLua:Upgrade (path) | +Upgrade the working copy format of the specified path. | +||
SubLua:GetWorkingCopyFormatVersion (path) | +Get the format version of the working copy at the specified path. | +||
SubLua:Checkout (url, path, options) | +Checks out a working copy from a url to a path. | +||
SubLua:Update (path, options) | +Updates the file or directory. | +||
SubLua:Export (srcPath, destPath, options) | +Create an unversioned copy of a tree. | +||
SubLua:Get (path, destPath, options) | +Retrieves the contents for a specific revision of a path and saves it to the destination file dstPath . |
+ ||
SubLua:Add (path, options) | +Schedule a working copy path for addition to the repository. | +||
SubLua:Import (path, url, message, options) | +Commit an unversioned file or tree into the repository. | +||
SubLua:MkDir (path, message) | +Create a new directory under version control. | +||
SubLua:Delete (path, message, options) | +Remove files and directories from version control. | +||
SubLua:Revert (path, options) | +Revert files and directories from version control. | +||
SubLua:Lock (path, message, options) | +Locks files and directories. | +||
SubLua:Unlock (path, options) | +Unlocks files and directories. | +||
SubLua:Commit (path, message, options) | +Commit files or directories into repository | +||
SubLua:DiffRevisions (path, startRevision, endRevision, summarizeCallback, options) | +Display the differences between two revisions of a path. | +||
SubLua:DiffPaths (path1, path2, summarizeCallback, options) | +Produce a diff summary which lists the changed items between path1@revision and path2@revision2 without creating text deltas. | +||
SubLua:Copy (srcPath, srcRevision, destPath) | +Copies one path to another | +||
SubLua:Info (path, infoReceiverCallback, options) | +Return information about pathOrUrl | +||
SubLua:PropGet (propertyName, path, options) | +Gets the value of a property on files, dirs, or revisions. | +||
SubLua:PropSet (propName, propValue, path, options) | +set property in @a path no matter whether local or repository | +||
SubLua:Cleanup (path) | +Recursively cleans up a local directory, finishing any + incomplete operations, removing lockfiles, etc. | +||
ReleaseWorkingCopyLock () | +Release the lock on the working copy + Does nothing and is unnecessary in svn versions 1.6 and below | +
Tables
+Options | +The Options class. | +
SvnInfo | +The SvnInfo class. | +
Enums | +The Subverison enums. |
+
+ + +
Functions
+ +-
+
- + + new (options) + +
-
+ Create a new SVN client to work with.
+
- Available listener callbacks are:
Notify( path, svn.action, kind, mimeType, contentState, propState, revision )
+ GetLogin( realm, username, password, maySave )
+ GetLogMessage()
and it should return the log message.
+ - Incomplete may not function
Cancel()
Parameters:
+-
+
- options + {table} [OPT] The table can contain key of 'username' for the username, 'password' for the password, 'no_auth_cache' to control if the is saved, and 'listener' which is a table of the listeners you want to overwride. +
Returns:
+-
+
+ {table} The SubLua object.
+
+ - + + SubLua:Cat (path, options) + +
-
+ Output the content of specified files or URLs.
+
+
Parameters:
+-
+
- path + {string} The path or URL to output. +
- options + {table} [OPT] Options table. Possible Options: peg, revision +
Returns:
+-
+
+ {string} String of the contents of
@path
. +
+ - + + SubLua:Upgrade (path) + +
-
+ Upgrade the working copy format of the specified path.
+
+
Parameters:
+-
+
- path + {string} The path to a working copy to upgrade. +
+ - + + SubLua:GetWorkingCopyFormatVersion (path) + +
-
+ Get the format version of the working copy at the specified path.
+
+
Parameters:
+-
+
- path + {string} The absolute path to a working copy to get the format for. +
Returns:
+-
+
+ The format version of the working copy specified (eg 29 for Subversion 1.7.13 and 31 for Subversion 1.8.0)
+
+ - + + SubLua:Checkout (url, path, options) + +
-
+ Checks out a working copy from a url to a path.
+
+
Parameters:
+-
+
- url + {string} The URL to check out. +
- path + {string} The local path to check out to. +
- options + {table} [OPT] Options table. Possible Options: peg, revision, depth, ignore_externals, force +
Returns:
+-
+
+ Revision of the checkout.
+
see also:
+-
+ Options
+
+ - + + SubLua:Update (path, options) + +
-
+ Updates the file or directory.
+
+
Parameters:
+-
+
- path + {string} The path or URL to output. +
- options + {table} [OPT] Options table. Possible Options: revision, depth, set_depth, ignore_externals, force +
Returns:
+-
+
+ Revision of the checkout.
+
see also:
+-
+ Options
+
+ - + + SubLua:Export (srcPath, destPath, options) + +
-
+ Create an unversioned copy of a tree.
+
+
Parameters:
+-
+
- srcPath + {string} The source path or URL to export from. +
- destPath + {string} The destination path to export to. +
- options + {table} [OPT] Options table. Possible Options: peg, revision, force, ignore_externals, depth, native_eol +
Returns:
+-
+
+ Revision of the Export.
+
see also:
+-
+ Options
+
+ - + + SubLua:Get (path, destPath, options) + +
-
+ Retrieves the contents for a specific
revision
of apath
and saves it to the destination filedstPath
. + +If
destPath
is empty (""), then this path will be constructed from the temporary directory on this system + and the filename inpath
.destPath
will still have the file extension frompath
and uniqueness of the + temporary filename will be ensured. + +Parameters:
+-
+
- path + {string} path or url +
- destPath + {string} destination path +
- options + {table} [OPT] Options table. Possible Options: peg, revision +
Returns:
+-
+
+ {string} the destPath or the temp file path if
destPath
was an empty string +see also:
+-
+ Options
+
+ - + + SubLua:Add (path, options) + +
-
+ Schedule a working copy path for addition to the repository. Adds a file to the repository.
+
+
Parameters:
+-
+
- path + {string} The path to add to the working copy. Path's parent must be under revision control already. +
- options + {table} [OPT] Options table. Possible Options: depth, force, no_ignore, add_parents +
see also:
+-
+ Options
+
+ - + + SubLua:Import (path, url, message, options) + +
-
+ Commit an unversioned file or tree into the repository. Parent directories are created as necessary in the repository.
+
+
Parameters:
+-
+
- path + {string} The path to add to the working copy. Path's parent must be under revision control already. +
- url + {string} The URL to check out. +
- message + {string} [OPT] The commit message. +
- options + {table} [OPT] Options table. Possible Options: depth, no_ignore, auto_props +
see also:
+-
+ Options
+
+ - + + SubLua:MkDir (path, message) + +
-
+ Create a new directory under version control.
+
+
Parameters:
+-
+
- path + {string}/{table} The path(s) or URL(s) to output. If it is a local path then the directory is created and + scheduled for addition upon the next commit. If it is a URL then the commit is immediate. +
- message + {string} [OPT] Log message as a string. +
+ - + + SubLua:Delete (path, message, options) + +
-
+ Remove files and directories from version control.
+
+
Parameters:
+-
+
- path + {string}/{table} The path(s) or URL(s) to remove. If it is a local path(s) then the paths are scheduled for + deletion upon the next commit. If it is a URL(s) then they are deleted immediatly. +
- message + {string} [OPT] Log message. +
- options + {table} [OPT] Options table. Possible Options: force, keep_local +
see also:
+-
+ Options
+
+ - + + SubLua:Revert (path, options) + +
-
+ Revert files and directories from version control.
+
+
Parameters:
+-
+
- path + {string}/{table} The path(s) or URL(s) to revert. +
- options + {table} [OPT] Options table. Possible Options: depth +
see also:
+-
+ Options
+
+ - + + SubLua:Lock (path, message, options) + +
-
+ Locks files and directories.
+
+
Parameters:
+-
+
- path + {string}/{table} The path(s) or URL(s) to lock. +
- message + {string} [OPT] The message for the lock. +
- options + {table} [OPT] Options table. Possible Options: force +
see also:
+-
+ Options
+
+ - + + SubLua:Unlock (path, options) + +
-
+ Unlocks files and directories.
+
+
Parameters:
+-
+
- path + {string}/{table} The path(s) or URL(s) to unlock. +
- options + {table} [OPT] Options table. Possible Options: force +
see also:
+-
+ Options
+
+ - + + SubLua:Commit (path, message, options) + +
-
+ Commit files or directories into repository
+
+
Parameters:
+-
+
- path + {string}/{table} The path(s) to commit. +
- message + {string} The commit message. +
- options + {table} [OPT] Options table. Possible Options: depth, keep_locks, keep_changelists +
see also:
+-
+ Options
+
+ - + + SubLua:DiffRevisions (path, startRevision, endRevision, summarizeCallback, options) + +
-
+ Display the differences between two revisions of a path.
+ The function may report false positives if
notice_ancestry
is true, since a file might have been modified between two revisions, + but still have the same contents. + IfsummarizeCallback
andoptions.summarize = true
then the results passed to thesummarizeCallback
. + IfsummarizeCallback
is not provided, butoptions.summarize = true
then an array of summaryInfo is returned. + +Parameters:
+-
+
- path + {string} The path to diff. Can be either a working-copy path or a URL. +
- startRevision + {string}/{number} The start revisions to check. +
- endRevision + {string}/{number} The end revision to check. +
- summarizeCallback
+
+
{function} [OPT] Called for every difference found when . + For each invocation it passes
+summaryInfo
table that has keyspath
,summarize_kind
,prop_changed
, andnode_kind
. ??Return true from the summarizeCallback() to have the function continue, if you return false it will stop the operation and throw an error.?? +summarize_kind
available values are found in the Enum section under the summarize.kind.node_kind
available values are found in the Enums section under node.kind + The prototype for the callback is
+function SubLua:MyDiffSummarizeReceiver( summaryInfo ) + print( summaryInfo.path, summaryInfo.summarize_kind, summaryInfo.prop_changed, summaryInfo.node_kind ) + return true +end +
+ - options + {table} [OPT] Options table. Possible Options: depth, summarize, peg, notice_ancestry, no_diff_deleted, ignore_content_type +
Returns:
+-
+
+ Normally the differences are returned as a {string} of file differences.
+
see also:
+ + + +
+ - + + SubLua:DiffPaths (path1, path2, summarizeCallback, options) + +
-
+ Produce a diff summary which lists the changed items between path1@revision and path2@revision2 without creating text deltas.
+ The function may report false positives if
notice_ancestry
is true, since a file might have been modified between two revisions, + but still have the same contents. + IfsummarizeCallback
andoptions.summarize = true
then the results passed to thesummarizeCallback
. + IfsummarizeCallback
is not provided, butoptions.summarize = true
then an array of summaryInfo is returned. + +Parameters:
+-
+
- path1 + {string} The first path to diff. Can be either a working-copy path or a URL. +
- path2 + {string} The second path to diff. Can be either a working-copy path or a URL. +
- summarizeCallback
+
+
{function} Called for every difference found. + For each invocation it passes
+summaryInfo
table that has keyspath
,summarize_kind
,prop_changed
, andnode_kind
. ??Return true from the summarizeCallback() to have the function continue, if you return false it will stop the operation and throw an error.?? +summarize_kind
available values are found in the Enum section under the summarize.kind.node_kind
available values are found in the Enums section under node.kind + The prototype for the callback is
+function SubLua:MyDiffSummarizeReceiver( summaryInfo ) + print( summaryInfo.path, summaryInfo.summarize_kind, summaryInfo.prop_changed, summaryInfo.node_kind ) + return true +end +
+ - options + {table} [OPT] Options table. Possible Options: depth, revision, revision2, summarize, peg, notice_ancestry. +
Returns:
+-
+
+ Normally the differences are returned as a {string} of file differences.
+
see also:
+ + + +
+ - + + SubLua:Copy (srcPath, srcRevision, destPath) + +
-
+ Copies one path to another
+
+
Parameters:
+-
+
- srcPath + {string} The source path to copy from. +
- srcRevision + {string} The source revision to copy from. +
- destPath + {string} The destination path to copy to. +
+ - + + SubLua:Info (path, infoReceiverCallback, options) + +
-
+ Return information about pathOrUrl
+
+
Parameters:
+-
+
- path + {string}/{table} The path or URL to get the specified information from. +
- infoReceiverCallback
+
+
{function} [OPT] Called everytime a file is found during receiving information. For each invocation it passes
+path
with the information present ininfo
. Return true from the InfoReceiver() to have the function contiue, if you return false it will stop the operation and throw an error. + The prototype for the callback is
+function SubLua:MyInfoReceiver( path, info ) + print( path, info ) + return true +end +
+ - options + {table} [OPT] Options table. Possible Options: peg, revision, depth +
see also:
+ + + +
+ - + + SubLua:PropGet (propertyName, path, options) + +
-
+ Gets the value of a property on files, dirs, or revisions.
+ property table. In the property table there are keys of property names to their value, both as strings.
+
+
Parameters:
+-
+
- propertyName + {string} The property to get from path. +
- path + {string}/{table} The path or URL to get the specified property from. +
- options + {table} [OPT] Options table. Possible Options: revision, depth +
Returns:
+-
+
+ {table} Table of properties. The list will contain tables where the keys are the path, and the values are a
+
see also:
+-
+ Options
+
+ - + + SubLua:PropSet (propName, propValue, path, options) + +
-
+ set property in @a path no matter whether local or repository
+
+
Parameters:
+-
+
- propName + {string} The property name to set. +
- propValue + {string} The value of the property to set +
- path + {string} Path where property to be set +
- options + {table} Options table. Possible Options: depth +
see also:
+-
+ Options
+
+ - + + SubLua:Cleanup (path) + +
-
+ Recursively cleans up a local directory, finishing any
+ incomplete operations, removing lockfiles, etc.
+
+
Parameters:
+-
+
- path + {string} Path to a local directory. +
+ - + + ReleaseWorkingCopyLock () + +
- + Release the lock on the working copy + Does nothing and is unnecessary in svn versions 1.6 and below + + + + + + + +
Tables
+ +-
+
- + + Options + +
-
+ The Options class.
+
+
Fields:
+-
+
- revision + the revision you want to update to; defaults to "HEAD". +
- revision2 + the revision for path2 when finding the differences in paths; defaults to "HEAD". +
- peg + the peg revision you want to update using; defaults to "HEAD". +
- depth + limit operation by depth ARG ('empty', 'files', 'immediates', or 'infinity'); defaults to unknown. +
- force + force operation to run; defaults to false. +
- ignore_externals + set to ignore the externals; defaults to false. +
- ignore_keywords + set to ignore the keyword substitution; defaults to false. +
- no_ignore + set to disregard default and svn:ignore property ignores; defaults to false. +
- native_eol + use a different EOL marker than the standard system marker for files with the svn:eol-style property set to 'native'. ARG may be one of 'LF', 'CR', 'CRLF'; defaults to NULL (no change). +
- set_depth
+ set new working copy depth to
depth
. Ifdepth
is not specified then this does nothing; defaults to false.
+ - parents + If true, create any non-existent parent directories also; defaults to false. +
- add_parents + If true, recurse up path's directory and look for a versioned directory. If found, add all intermediate paths between it and path; defaults to false. +
- keep_changelists + After the commit completes successfully, remove changelist associations from the targets, unless keep_changelists is true; defaults to false. +
- keep_locks + Unlock paths in the repository, unless keep_locks is true; defaults to false. +
- auto_props + Enable automatic properties; defaults to true. +
- ignore_unknown_node_types + Ignore files of which the node type is unknown, such as device files and pipes.; defaults to false. +
- keep_local + if true then the paths will not be removed from the working copy, only scheduled for removal from the repository. Once the scheduled deletion is committed, they will appear as unversioned paths in the working copy; defaults to false. +
- no_diff_deleting + Do not print differences for deleted files; defaults to false. +
- notice_ancestry + Notice ancestry when calculating differences; defaults to false. +
- ignore_content_type + if true, Diff output will be generated for binary files, in which case diffs will be shown regardless of the content types; defaults to false. +
- summarize + if true, during a diff operation only produce a summary which lists the changed items without creating text deltas; defaults to false. +
+ - + + SvnInfo + +
-
+ The SvnInfo class.
+
+
Fields:
+-
+
- isValid + Is it valid? +
- url + The url +
- revision + The revision +
- kind + The kind +
- rootUrl + The root URL +
- uuid + The uuid +
- lastChangedRevision + Last changed revision +
- lastChangedDate + Last changed date +
- lastChangedAuthor + Last changed author +
- lock + Lock +
- hasWCInfo + Has working copy info? +
- schedule + The schedule +
- copyFromUrl + Copy from URL +
- copyFromRev + Copy from revision +
- textTime + Time in text +
- propTime + Properties time +
- checksum + The checksum +
- conflictOld + Conflict old +
- conflictNew + Conflict new +
- conflictWrk + Conflict working +
- propRejectFile + Prop reject file +
- changelist + The changelist +
- depth + Depth +
- workingSize + Working size +
- size + Size +
- size64 + Size64 +
- workingSize64 + Working Size64 +
- treeConflict + the TreeConflict +
+ - + + Enums + +
-
+ The Subverison enums. These are part of the SubLua object returned after calling new().
+
+
Fields:
+-
+
- wc.notify.action.add + Adding a path to revision control. +
- wc.notify.action.copy + Copying a versioned path. +
- wc.notify.action.delete + Deleting a versioned path. +
- wc.notify.action.restore + Restoring a missing path from the pristine text-base. +
- wc.notify.action.revert + Reverting a modified path. +
- wc.notify.action.failed_revert + A revert operation has failed. +
- wc.notify.action.resolved + Resolving a conflict. +
- wc.notify.action.skip + Skipping a path. +
- wc.notify.action.update_delete + Got a delete in an update. +
- wc.notify.action.update_add + Got an add in an update. +
- wc.notify.action.update_update + Got any other action in an update. +
- wc.notify.action.update_completed + The last notification in an update (including updates of externals). +
- wc.notify.action.update_external + Updating an external module. +
- wc.notify.action.status_completed + The last notification in a status (including status on externals). +
- wc.notify.action.status_external + Running status on an external module. +
- wc.notify.action.commit_modified + Committing a modification. +
- wc.notify.action.commit_added + Committing an addition. +
- wc.notify.action.commit_deleted + Committing a deletion. +
- wc.notify.action.commit_replaced + Committing a replacement. +
- wc.notify.action.commit_postfix_txdelta + Transmitting post-fix text-delta data for a file. +
- wc.notify.action.blame_revision + Processed a single revision's blame. +
- wc.notify.action.locked + Locking a path. New in 1.2. +
- wc.notify.action.unlocked + Unlocking a path. New in 1.2. +
- wc.notify.action.failed_lock + Failed to lock a path. New in 1.2. +
- wc.notify.action.failed_unlock + Failed to unlock a path. New in 1.2. +
- wc.notify.action.exists + Tried adding a path that already exists. New in 1.5. +
- wc.notify.action.changelist_set + Changelist name set. New in 1.5. +
- wc.notify.action.changelist_clear + Changelist name cleared. New in 1.5. +
- wc.notify.action.changelist_moved + Warn user that a path has moved from one changelist to another. New in 1.5. Deprecated: As of 1.7, separate clear and set notifications are sent. +
- wc.notify.action.merge_begin + A merge operation (to path) has begun. +
- wc.notify.action.foreign_merge_begin + A merge operation (to path) from a foreign repository has begun. +
- wc.notify.action.update_replace + Replace notification. New in 1.5. +
- wc.notify.action.property_added + Property added. New in 1.6. +
- wc.notify.action.property_modified + Property updated. New in 1.6. +
- wc.notify.action.property_deleted + Property deleted. New in 1.6. +
- wc.notify.action.property_deleted_nonexistentNonexistent + property deleted. New in 1.6. +
- wc.notify.action.revprop_set + Revprop set. New in 1.6. +
- wc.notify.action.revprop_deleted + Revprop deleted. New in 1.6. +
- wc.notify.action.merge_completed + The last notification in a merge. New in 1.6. +
- wc.notify.action.tree_conflict + The path is a tree-conflict victim of the intended action (not a persistent tree-conflict from an earlier operation, but this operation caused the tree-conflict). New in 1.6. +
- wc.notify.action.failed_external + The path is a subdirectory referenced in an externals definition which is unable to be operated on. New in 1.6. +
- wc.notify.action.update_started + Starting an update operation. New in 1.7. +
- wc.notify.action.update_skip_obstruction + An update tried to add a file or directory at a path where a separate working copy was found. New in 1.7. +
- wc.notify.action.update_skip_working_only + An explicit update tried to update a file or directory that doesn't live in the repository and can't be brought in. New in 1.7. +
- wc.notify.action.update_skip_access_denied + An update tried to update a file or directory to which access could not be obtained. New in 1.7. +
- wc.notify.action.update_external_removed + An update operation removed an external working copy. New in 1.7. +
- wc.notify.action.update_shadowed_add + A node below an existing node was added during update. New in 1.7. +
- wc.notify.action.update_shadowed_update + A node below an exising node was updated during update. New in 1.7. +
- wc.notify.action.update_shadowed_delete + A node below an existing node was deleted during update. New in 1.7. +
- wc.notify.action.merge_record_info + The mergeinfo on path was updated. New in 1.7. +
- wc.notify.action.upgraded_path + An working copy directory was upgraded to the latest format. New in 1.7. +
- wc.notify.action.merge_record_info_begin + Mergeinfo describing a merge was recorded. New in 1.7. +
- wc.notify.action.merge_elide_info + Mergeinfo was removed due to elision. New in 1.7. +
- wc.notify.action.patch + A file in the working copy was patched. New in 1.7. +
- wc.notify.action.patch_applied_hunk + A hunk from a patch was applied. New in 1.7. +
- wc.notify.action.patch_rejected_hunk + A hunk from a patch was rejected. New in 1.7. +
- wc.notify.action.patch_hunk_already_applied + A hunk from a patch was found to already be applied. New in 1.7. +
- wc.notify.action.commit_copied + Committing a non-overwriting copy (path is the target of the copy, not the source). New in 1.7. +
- wc.notify.action.commit_copied_replaced + Committing an overwriting (replace) copy (path is the target of the copy, not the source). New in 1.7. +
- wc.notify.action.url_redirect + The server has instructed the client to follow a URL redirection. New in 1.7. +
- wc.notify.action.path_nonexistent + The operation was attempted on a path which doesn't exist. New in 1.7. +
- wc.notify.action.exclude + Removing a path by excluding it. New in 1.7. +
- wc.notify.action.failed_conflict + Operation failed because the node remains in conflict. New in 1.7. +
- wc.notify.action.failed_missing + Operation failed because an added node is missing. New in 1.7. +
- wc.notify.action.failed_out_of_date + Operation failed because a node is out of date. New in 1.7. +
- wc.notify.action.failed_no_parent + Operation failed because an added parent is not selected. New in 1.7. +
- wc.notify.action.failed_locked + Operation failed because a node is locked by another user and/or working copy. New in 1.7. +
- wc.notify.action.failed_forbidden_by_server + Operation failed because the operation was forbidden by the server. New in 1.7. +
- wc.notify.action.skip_conflicted + The operation skipped the path because it was conflicted. New in 1.7. +
- wc.notify.state.unknown + Notifier doesn't know or isn't saying. +
- wc.notify.state.unchanged + The state did not change. +
- wc.notify.state.missing + The item wasn't present. +
- wc.notify.state.obstructed + An unversioned item obstructed work. +
- wc.notify.state.changed + Pristine state was modified. +
- wc.notify.state.merged + Modified state had mods merged in. +
- wc.notify.state.conflicted + Modified state got conflicting mods. +
- wc.notify.state.source_missing + The source to copy the file from is missing. New in 1.7 +
- wc.schedule.normal + Nothing special here. +
- wc.schedule.add + Slated for addition. +
- wc.schedule.delete + Slated for deletion. +
- wc.schedule.replace + Slated for replacement (delete + add) +
- wc.conflict.action.edit + attempting to change text or props. +
- wc.conflict.action.add + attempting to add object. +
- wc.conflict.action.delete + attempting to delete object. +
- wc.conflict.action.replace + attempting to replace object. New in 1.7 +
- wc.conflict.reason.edited + Local edits are already present. +
- wc.conflict.reason.obstructed + Another object is in the way. +
- wc.conflict.reason.deleted + Object is already schedule-delete. +
- wc.conflict.reason.missing + Object is unknown or missing. +
- wc.conflict.reason.unversioned + Object is unversioned. +
- wc.conflict.reason.added + Object is already added or schedule-add. +
- wc.conflict.reason.replaced + Object is already replaced. New in 1.7 +
- wc.conflict.kind.text + textual conflict (on a file) +
- wc.conflict.kind.property + property conflict (on a file or dir) +
- wc.conflict.kind.tree + tree conflict (on a dir) +
- wc.operation.none + No user operation exposed a conflict. +
- wc.operation.update + User operation 'update' exposed a conflict. +
- wc.operation.switch + User operation 'switch' exposed a conflict. +
- wc.operation.merge + User operation 'merge' exposed a conflict. +
- node.kind.none + Absent node in the Subversion filesystem. +
- node.kind.file + Regular file node in the Subversion filesystem. +
- node.kind.dir + Directory node in the Subversion filesystem. +
- node.kind.unknown + "something's here, but we don't know what" node in the Subversion filesystem. +
- node.action.change + Changed "action" attached to nodes in the dumpfile. +
- node.action.add + Added "action" attached to nodes in the dumpfile. +
- node.action.delete + Deleted "action" attached to nodes in the dumpfile. +
- node.action.replace + Replaced "action" attached to nodes in the dumpfile. +
- depth.unknown + Depth undetermined or ignored. In some contexts, this means the client should choose an appropriate default depth. The server will generally treat it as depth.infinity. +
- depth.exclude + Exclude (i.e., don't descend into) directory D. +
- depth.empty + Just the named directory D, no entries. Updates will not pull in any files or subdirectories not already present. +
- depth.files + D + its file children, but not subdirs. Updates will pull in any files not already present, but not subdirectories. +
- depth.immediates + D + immediate children (D and its entries). Updates will pull in any files or subdirectories not already present; those subdirectories' this_dir entries will have depth-empty. +
- depth.infinity + D + all descendants (full recursion from D). Updates will pull in any files or subdirectories not already present; those subdirectories' this_dir entries will have depth-infinity. Equivalent to the pre-1.5 default update behavior. +
- recurse.kind.nonrecursive + Indicates recursion is NOT needed. +
- recurse.kind.recursive + Indicates recursion is needed. +
- summarize.kind.normal + An item with no text modifications. @see SubLua:DiffRevisions @see SubLua:DiffPaths +
- summarize.kind.added + An added item. @see SubLua:DiffRevisions @see SubLua:DiffPaths +
- summarize.kind.modified + An item with text modifications. @see SubLua:DiffRevisions @see SubLua:DiffPaths +
- summarize.kind.deleted + A deleted item. @see SubLua:DiffRevisions @see SubLua:DiffPaths +
+
Module SubLua
-
-Lua binding to SubCpp - a C++ Subversion library.
- -Author: -
Ryan P. |
Release: 1.00 <04/21/2010>
- - - -Functions
-SubLua.ReleaseWorkingCopyLock () | -Release the lock on the working copy Does nothing and is unnecessary in svn versions 1.6 and below | -
SubLua:Add (path, options) | -Schedule a working copy path for addition to the repository. | -
SubLua:Cat (path, options) | -Output the content of specified files or URLs. | -
SubLua:Checkout (url, path, options) | -Checks out a working copy from a url to a path. | -
SubLua:Cleanup (path) | -Recursively cleans up a local directory, finishing any incomplete operations, removing lockfiles, etc. | -
SubLua:Commit (path, message, options) | -Commit files or directories into repository | -
SubLua:Copy (srcPath, srcRevision, destPath) | -Copies one path to another | -
SubLua:Delete (path, options) | -Remove files and directories from version control. | -
SubLua:Diff (tmpPath, path, revision1, revision2, options) | -Display the differences between two revisions or paths. | -
SubLua:Export (srcPath, destPath, options) | -Create an unversioned copy of a tree. | -
SubLua:Get (path, destPath, options) | -Retrieves the contents for a specific revision of a path and saves it to the destination file dstPath. | -
SubLua:Import (path, url, message, options) | -Commit an unversioned file or tree into the repository. | -
SubLua:Info (path, infoReceiverCallback, options) | -Return information about pathOrUrl | -
SubLua:Lock (path, options) | -Locks files and directories. | -
SubLua:MkDir (path, message) | -Create a new directory under version control. | -
SubLua:PropGet (propertyName, path, options) | -Gets the value of a property on files, dirs, or revisions. | -
SubLua:PropSet (propName, propValue, path, options) | -- |
SubLua:Revert (path, options) | -Revert files and directories from version control. | -
SubLua:Unlock (path, options) | -Unlocks files and directories. | -
SubLua:Update (path, options) | -Updates the file or directory. | -
Tables
-Enums | -The Subverison enums. | -
Options | -The Options class. | -
SvnInfo | -The SvnInfo class. | -
-
- - - -
Functions
--
-
-
-
-
- SubLua.ReleaseWorkingCopyLock () -
- -Release the lock on the working copy Does nothing and is unnecessary in svn versions 1.6 and below - - - - - - - - - - - - - - -
- SubLua:Add (path, options) -
-
-Schedule a working copy path for addition to the repository. Adds a file to the repository.
-
-
-
Parameters
--
-
-
- - path: {string} The path to add to the working copy. Path's parent must be under revision control already. - - -
- - options: {table} [OPT] Options table. Possible Options: depth, force, no_ignore, add_parents - - -
See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Cat (path, options) -
-
-Output the content of specified files or URLs.
-
-
-
Parameters
--
-
-
- - path: {string} The path or URL to output. - - -
- - options: {table} [OPT] Options table. Possible Options: peg, revision - - -
Return value:
-{string} String of the contents of `@path`. - - - -
-
-
-
-
- - SubLua:Checkout (url, path, options) -
-
-Checks out a working copy from a url to a path.
-
-
-
Parameters
--
-
-
- - url: {string} The URL to check out. - - -
- - path: {string} The local path to check out to. - - -
- - options: {table} [OPT] Options table. Possible Options: peg, revision, depth, ignore_externals, force - - -
Return value:
-Revision of the checkout. - - - -See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Cleanup (path) -
-
-Recursively cleans up a local directory, finishing any incomplete operations, removing lockfiles, etc.
-
-
-
Parameters
--
-
-
- - path: {string} Path to a local directory. - - -
-
-
-
-
- - SubLua:Commit (path, message, options) -
-
-Commit files or directories into repository
-
-
-
Parameters
--
-
-
- - path: {string}/{table} The path(s) to commit. - - -
- - message: {string} The commit message. - - -
- - options: {table} [OPT] Options table. Possible Options: depth, keep_locks, keep_changelists - - -
See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Copy (srcPath, srcRevision, destPath) -
-
-Copies one path to another
-
-
-
Parameters
--
-
-
- - srcPath: {string} The source path to copy from. - - -
- - srcRevision: {string} The source revision to copy from. - - -
- - destPath: {string} The destination path to copy to. - - -
-
-
-
-
- - SubLua:Delete (path, options) -
-
-Remove files and directories from version control.
-
-
-
Parameters
--
-
-
- - path: {string}/{table} The path(s) or URL(s) to remove. If it is a local path(s) then the paths are scheduled for deletion upon the next commit. If it is a URL(s) then they are deleted immediatly. - - -
- - options: {table} [OPT] Options table. Possible Options: force, keep_local - - -
See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Diff (tmpPath, path, revision1, revision2, options) -
-
-Display the differences between two revisions or paths.
-
-
-
Parameters
--
-
-
- - tmpPath: {string} The first path to diff. - - -
- - path: {string} The second path to diff. - - -
- - revision1: {string}/{number} [OPT] The revision of the first file to diff. - - -
- - revision2: {string}/{number} [OPT] The revision of the second file to diff. - - -
- - options: {table} [OPT] Options table. Possible Options: depth, notice_ancestry, no_diff_deleted - - -
See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Export (srcPath, destPath, options) -
-
-Create an unversioned copy of a tree.
-
-
-
Parameters
--
-
-
- - srcPath: {string} The source path or URL to export from. - - -
- - destPath: {string} The destination path to export to. - - -
- - options: {table} [OPT] Options table. Possible Options: peg, revision, force, ignore_externals, depth, native_eol - - -
Return value:
-Revision of the Export. - - - -See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Get (path, destPath, options) -
-
-Retrieves the contents for a specific revision of a path and saves it to the destination file dstPath. If
is empty (""), then this path will be constructed from the temporary directory on this system and the filename in . will still have the file extension from and uniqueness of the temporary filename will be ensured. - - - Parameters
--
-
-
- - path: {string} path or url - - -
- - destPath: {string} destination path - - -
- - options: {table} [OPT] Options table. Possible Options: peg, revision - - -
Return value:
-{string} the destPath or the temp file path ifwas an empty string - - - - See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Import (path, url, message, options) -
-
-Commit an unversioned file or tree into the repository. Parent directories are created as necessary in the repository.
-
-
-
Parameters
--
-
-
- - path: {string} The path to add to the working copy. Path's parent must be under revision control already. - - -
- - url: {string} The URL to check out. - - -
- - message: {string} [OPT] The commit message. - - -
- - options: {table} [OPT] Options table. Possible Options: depth, no_ignore, auto_props - - -
See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Info (path, infoReceiverCallback, options) -
-
-Return information about pathOrUrl
-
-
-
Parameters
--
-
-
- - path: {string}/{table} The path or URL to get the specified information from. - - -
-
- infoReceiverCallback: {function} [OPT] Called everytime a file is found during receiving information. For each invocation it passes path with the information present in info. Return true from the InfoReceiver() to have the function contiue, if you return false it will stop the operation and throw an error.
The prototype for the callback is
function SubLua:MyInfoReceiver( path, info )
        print( path, info )
        return true
end -
-
- - - options: {table} [OPT] Options table. Possible Options: peg, revision, depth - - -
See also:
--
-
-
- - Options - - -
- - SvnInfo - - -
-
-
-
-
- - SubLua:Lock (path, options) -
-
-Locks files and directories.
-
-
-
Parameters
--
-
-
- - path: {string}/{table} The path(s) or URL(s) to revert. - - -
- - options: {table} [OPT] Options table. Possible Options: force - - -
See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:MkDir (path, message) -
-
-Create a new directory under version control.
-
-
-
Parameters
--
-
-
- - path: {string}/{table} The path(s) or URL(s) to output. If it is a local path then the directory is created and scheduled for addition upon the next commit. If it is a URL then the commit is immediate. - - -
- - message: {string} [OPT] Log message as a string. - - -
-
-
-
-
- - SubLua:PropGet (propertyName, path, options) -
-
-Gets the value of a property on files, dirs, or revisions.
-
-
-
Parameters
--
-
-
- - propertyName: {string} The property to get from path. - - -
- - path: {string}/{table} The path or URL to get the specified property from. - - -
- - options: {table} [OPT] Options table. Possible Options: revision, depth - - -
Return value:
-{table} Table of properties. The list will contain tables where the keys are the path, and the values are a property table. In the property table there are keys of property names to their value, both as strings. - - - -See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:PropSet (propName, propValue, path, options) -
-
-
-
-
-
Parameters
--
-
-
- - propName: {string} The property name to set. - - -
- - propValue: {string} The value of the property to set - - -
- - path: {string} Path where property to be set - - -
- - options: {table} Options table. Possible Options: depth - - -
See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Revert (path, options) -
-
-Revert files and directories from version control.
-
-
-
Parameters
--
-
-
- - path: {string}/{table} The path(s) or URL(s) to revert. - - -
- - options: {table} [OPT] Options table. Possible Options: depth - - -
See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Unlock (path, options) -
-
-Unlocks files and directories.
-
-
-
Parameters
--
-
-
- - path: {string}/{table} The path(s) or URL(s) to revert. - - -
- - options: {table} [OPT] Options table. Possible Options: force - - -
See also:
--
-
-
- - Options - - -
-
-
-
-
- - SubLua:Update (path, options) -
-
-Updates the file or directory.
-
-
-
Parameters
--
-
-
- - path: {string} The path or URL to output. - - -
- - options: {table} [OPT] Options table. Possible Options: revision, depth, set_depth, ignore_externals, force - - -
Return value:
-Revision of the checkout. - - - -See also:
--
-
-
- - Options - - -
-
-
-
Tables
--
-
-
- Enums -
- The Subverison enums. These are part of the SubLua object returned after calling new().
-
-
-Fields
-
-
-
-
- - wc.notify.action.add: Adding a path to revision control. - - -
- - wc.notify.action.copy: Copying a versioned path. - - -
- - wc.notify.action.delete: Deleting a versioned path. - - -
- - wc.notify.action.restore: Restoring a missing path from the pristine text-base. - - -
- - wc.notify.action.revert: Reverting a modified path. - - -
- - wc.notify.action.failed_revert: A revert operation has failed. - - -
- - wc.notify.action.resolved: Resolving a conflict. - - -
- - wc.notify.action.skip: Skipping a path. - - -
- - wc.notify.action.update_delete: Got a delete in an update. - - -
- - wc.notify.action.update_add: Got an add in an update. - - -
- - wc.notify.action.update_update: Got any other action in an update. - - -
- - wc.notify.action.update_completed: The last notification in an update (including updates of externals). - - -
- - wc.notify.action.update_external: Updating an external module. - - -
- - wc.notify.action.status_completed: The last notification in a status (including status on externals). - - -
- - wc.notify.action.status_external: Running status on an external module. - - -
- - wc.notify.action.commit_modified: Committing a modification. - - -
- - wc.notify.action.commit_added: Committing an addition. - - -
- - wc.notify.action.commit_deleted: Committing a deletion. - - -
- - wc.notify.action.commit_replaced: Committing a replacement. - - -
- - wc.notify.action.commit_postfix_txdelta: Transmitting post-fix text-delta data for a file. - - -
- - wc.notify.action.blame_revision: Processed a single revision's blame. - - -
- - wc.notify.action.locked: Locking a path. New in 1.2. - - -
- - wc.notify.action.unlocked: Unlocking a path. New in 1.2. - - -
- - wc.notify.action.failed_lock: Failed to lock a path. New in 1.2. - - -
- - wc.notify.action.failed_unlock: Failed to unlock a path. New in 1.2. - - -
- - wc.notify.action.exists: Tried adding a path that already exists. New in 1.5. - - -
- - wc.notify.action.changelist_set: Changelist name set. New in 1.5. - - -
- - wc.notify.action.changelist_clear: Changelist name cleared. New in 1.5. - - -
- - wc.notify.action.changelist_moved: Warn user that a path has moved from one changelist to another. New in 1.5. Deprecated: As of 1.7, separate clear and set notifications are sent. - - -
- - wc.notify.action.merge_begin: A merge operation (to path) has begun. See svn_-- @field wc.notify.action.merge_range. New in 1.5. - - -
- - wc.notify.action.foreign_merge_begin: A merge operation (to path) from a foreign repository has begun. See -- @field wc.notify.action.merge_range. New in 1.5. - - -
- - wc.notify.action.update_replace: Replace notification. New in 1.5. - - -
- - wc.notify.action.property_added: Property added. New in 1.6. - - -
- - wc.notify.action.property_modified: Property updated. New in 1.6. - - -
- - wc.notify.action.property_deleted: Property deleted. New in 1.6. - - -
- - wc.notify.action.property_deleted_nonexistentNonexistent: property deleted. New in 1.6. - - -
- - wc.notify.action.revprop_set: Revprop set. New in 1.6. - - -
- - wc.notify.action.revprop_deleted: Revprop deleted. New in 1.6. - - -
- - wc.notify.action.merge_completed: The last notification in a merge. New in 1.6. - - -
- - wc.notify.action.tree_conflict: The path is a tree-conflict victim of the intended action (*not* a persistent tree-conflict from an earlier operation, but *this* operation caused the tree-conflict). New in 1.6. - - -
- - wc.notify.action.failed_external: The path is a subdirectory referenced in an externals definition which is unable to be operated on. New in 1.6. - - -
- - wc.notify.action.update_started: Starting an update operation. New in 1.7. - - -
- - wc.notify.action.update_skip_obstruction: An update tried to add a file or directory at a path where a separate working copy was found. New in 1.7. - - -
- - wc.notify.action.update_skip_working_only: An explicit update tried to update a file or directory that doesn't live in the repository and can't be brought in. New in 1.7. - - -
- - wc.notify.action.update_skip_access_denied: An update tried to update a file or directory to which access could not be obtained. New in 1.7. - - -
- - wc.notify.action.update_external_removed: An update operation removed an external working copy. New in 1.7. - - -
- - wc.notify.action.update_shadowed_add: A node below an existing node was added during update. New in 1.7. - - -
- - wc.notify.action.update_shadowed_update: A node below an exising node was updated during update. New in 1.7. - - -
- - wc.notify.action.update_shadowed_delete: A node below an existing node was deleted during update. New in 1.7. - - -
- - wc.notify.action.merge_record_info: The mergeinfo on path was updated. New in 1.7. - - -
- - wc.notify.action.upgraded_path: An working copy directory was upgraded to the latest format. New in 1.7. - - -
- - wc.notify.action.merge_record_info_begin: Mergeinfo describing a merge was recorded. New in 1.7. - - -
- - wc.notify.action.merge_elide_info: Mergeinfo was removed due to elision. New in 1.7. - - -
- - wc.notify.action.patch: A file in the working copy was patched. New in 1.7. - - -
- - wc.notify.action.patch_applied_hunk: A hunk from a patch was applied. New in 1.7. - - -
- - wc.notify.action.patch_rejected_hunk: A hunk from a patch was rejected. New in 1.7. - - -
- - wc.notify.action.patch_hunk_already_applied: A hunk from a patch was found to already be applied. New in 1.7. - - -
- - wc.notify.action.commit_copied: Committing a non-overwriting copy (path is the target of the copy, not the source). New in 1.7. - - -
- - wc.notify.action.commit_copied_replaced: Committing an overwriting (replace) copy (path is the target of the copy, not the source). New in 1.7. - - -
- - wc.notify.action.url_redirect: The server has instructed the client to follow a URL redirection. New in 1.7. - - -
- - wc.notify.action.path_nonexistent: The operation was attempted on a path which doesn't exist. New in 1.7. - - -
- - wc.notify.action.exclude: Removing a path by excluding it. New in 1.7. - - -
- - wc.notify.action.failed_conflict: Operation failed because the node remains in conflict. New in 1.7. - - -
- - wc.notify.action.failed_missing: Operation failed because an added node is missing. New in 1.7. - - -
- - wc.notify.action.failed_out_of_date: Operation failed because a node is out of date. New in 1.7. - - -
- - wc.notify.action.failed_no_parent: Operation failed because an added parent is not selected. New in 1.7. - - -
- - wc.notify.action.failed_locked: Operation failed because a node is locked by another user and/or working copy. New in 1.7. - - -
- - wc.notify.action.failed_forbidden_by_server: Operation failed because the operation was forbidden by the server. New in 1.7. - - -
- - wc.notify.action.skip_conflicted: The operation skipped the path because it was conflicted. New in 1.7. - - -
-
-
- - Options -
- The Options class.
-
-
-Fields
-
-
-
-
- - revision: the revision you want to update to; defaults to "HEAD". - - -
- - peg: the peg revision you want to update using; defaults to "HEAD". - - -
- - depth: limit operation by depth ARG ('empty', 'files', 'immediates', or 'infinity'); defaults to unknown. - - -
- - force: force operation to run; defaults to false. - - -
- - ignore_externals: set to ignore the externals; defaults to false. - - -
- - ignore_keywords: set to ignore the keyword substitution; defaults to false. - - -
- - no_ignore: set to disregard default and svn:ignore property ignores; defaults to false. - - -
- - native_eol: use a different EOL marker than the standard system marker for files with the svn:eol-style property set to 'native'. ARG may be one of 'LF', 'CR', 'CRLF'; defaults to NULL (no change). - - -
- - set_depth: set new working copy depth to depth. If depth is not specified then this does nothing; defaults to false. - - -
- - parents: If true, create any non-existent parent directories also; defaults to false. - - -
- - add_parents: If true, recurse up path's directory and look for a versioned directory. If found, add all intermediate paths between it and path; defaults to false. - - -
- - keep_changelists: After the commit completes successfully, remove changelist associations from the targets, unless keep_changelists is true; defaults to false. - - -
- - keep_locks: Unlock paths in the repository, unless keep_locks is true; defaults to false. - - -
- - auto_props: Enable automatic properties; defaults to true. - - -
- - ignore_unknown_node_types: Ignore files of which the node type is unknown, such as device files and pipes.; defaults to false. - - -
- - keep_local: if true then the paths will not be removed from the working copy, only scheduled for removal from the repository. Once the scheduled deletion is committed, they will appear as unversioned paths in the working copy; defaults to false. - - -
- - no_diff_deleting: Do not print differences for deleted files; defaults to false. - - -
- - notice_ancestry: Notice ancestry when calculating differences; defaults to false. - - -
-
-
- - SvnInfo -
- The SvnInfo class.
-
-
-Fields
-
-
-
-
- - isValid: Is it valid? - - -
- - url: The url - - -
- - revision: The revision - - -
- - kind: The kind - - -
- - rootUrl: The root URL - - -
- - uuid: The uuid - - -
- - lastChangedRevision: Last changed revision - - -
- - lastChangedDate: Last changed date - - -
- - lastChangedAuthor: Last changed author - - -
- - lock: Lock - - -
- - hasWCInfo: Has working copy info? - - -
- - schedule: The schedule - - -
- - copyFromUrl: Copy from URL - - -
- - copyFromRev: Copy from revision - - -
- - textTime: Time in text - - -
- - propTime: Properties time - - -
- - checksum: The checksum - - -
- - conflictOld: Conflict old - - -
- - conflictNew: Conflict new - - -
- - conflictWrk: Conflict working - - -
- - propRejectFile: Prop reject file - - -
- - changelist: The changelist - - -
- - depth: Depth - - -
- - workingSize: Working size - - -
- - size: Size - - -
- - size64: Size64 - - -
- - workingSize64: Working Size64 - - -
- - treeConflict: the TreeConflict - - -
-
-
-
--- year,month,day,hour,min,sec +-- @param t a table containing one of the following keys and a value: +-- one of `year`,`month`,`day`,`hour`,`min`,`sec` -- @return this date function Date:add(t) + local old_dst = self.tab.isdst local key,val = next(t) self.tab[key] = self.tab[key] + val self:set(os_time(self.tab)) + if old_dst ~= self.tab.isdst then + self.tab.hour = self.tab.hour - (old_dst and 1 or -1) + self:set(os_time(self.tab)) + end return self end @@ -232,35 +253,32 @@ function Date:last_day() end --- difference between two Date objects. --- Note: currently the result is a regular @{Date} object, --- but also has `interval` field set, which means a more --- appropriate string rep is used. --- @param other Date object --- @return a Date object +-- @tparam Date other Date object +-- @treturn Date.Interval object function Date:diff(other) local dt = self.time - other.time if dt < 0 then error("date difference is negative!",2) end - return Date(dt,true) + return Date.Interval(dt) end --- long numerical ISO data format version of this date. --- If it's an interval then the format is '2 hours 29 sec' etc. function Date:__tostring() - if not self.interval then - return os_date('%Y-%m-%d %H:%M:%S',self.time) + local t = os_date('%Y-%m-%dT%H:%M:%S',self.time) + if self.utc then + return t .. 'Z' else - local t, res = self.tab, '' - local y,m,d = t.year - 1970, t.month - 1, t.day - 1 - if y > 0 then res = res .. y .. ' years ' end - if m > 0 then res = res .. m .. ' months ' end - if d > 0 then res = res .. d .. ' days ' end - if y == 0 and m == 0 then - local h = t.hour - if h > 0 then res = res .. h .. ' hours ' end - if t.min > 0 then res = res .. t.min .. ' min ' end - if t.sec > 0 then res = res .. t.sec .. ' sec ' end + local offs = self:tzone() + if offs == 0 then + return t .. 'Z' + end + local sign = offs > 0 and '+' or '-' + local h = math.ceil(offs/3600) + local m = (offs % 3600)/60 + if m == 0 then + return t .. ('%s%02d'):format(sign,h) + else + return t .. ('%s%02d:%02d'):format(sign,h,m) end - return res end end @@ -269,11 +287,63 @@ function Date:__eq(other) return self.time == other.time end ---- equality between Date objects. +--- ordering between Date objects. function Date:__lt(other) return self.time < other.time end +--- difference between Date objects. +-- @function Date:__sub +Date.__sub = Date.diff + +--- add a date and an interval. +-- @param other either a `Date.Interval` object or a table such as +-- passed to `Date:add` +function Date:__add(other) + local nd = Date(self) + if Date.Interval:class_of(other) then + other = {sec=other.time} + end + nd:add(other) + return nd +end + +Date.Interval = class(Date) + +---- Date.Interval constructor +-- @int t an interval in seconds +-- @function Date.Interval +function Date.Interval:_init(t) + self:set(t) +end + +function Date.Interval:set(t) + self.time = t + self.tab = os_date('!*t',self.time) +end + +local function ess(n) + if n > 1 then return 's ' + else return ' ' + end +end + +--- If it's an interval then the format is '2 hours 29 sec' etc. +function Date.Interval:__tostring() + local t, res = self.tab, '' + local y,m,d = t.year - 1970, t.month - 1, t.day - 1 + if y > 0 then res = res .. y .. ' year'..ess(y) end + if m > 0 then res = res .. m .. ' month'..ess(m) end + if d > 0 then res = res .. d .. ' day'..ess(d) end + if y == 0 and m == 0 then + local h = t.hour + if h > 0 then res = res .. h .. ' hour'..ess(h) end + if t.min > 0 then res = res .. t.min .. ' min ' end + if t.sec > 0 then res = res .. t.sec .. ' sec ' end + end + if res == '' then res = 'zero' end + return res +end ------------ Date.Format class: parsing and renderinig dates ------------ @@ -287,34 +357,37 @@ local formats = { S = {'sec',{true,true}}, } --- - --- Date.Format constructor. --- @param fmt. A string where the following fields are significant:
-
---
- d day (either d or dd) ---
- y year (either yy or yyy) ---
- m month (either m or mm) ---
- H hour (either H or HH) ---
- M minute (either M or MM) ---
- S second (either S or SS) ---
-
---
- ISO 8601, --- like 2010-05-10 12:35:23Z or 2008-10-03T14:30+02
- ---
- times like 15:30 or 8.05pm (assumed to be today's date) ---
- dates like 28/10/02 (European order!) or 5 Feb 2012 ---
- month name like march or Mar (case-insensitive, first 3 letters); --- here the day will be 1 and the year this current year ---
+--- join the elements of a list using a delimiter. -- This method uses tostring on all elements. --- @param delim a delimiter string, can be empty. +-- @string[opt=''] delim a delimiter string, can be empty. -- @return a string function List:join (delim) delim = delim or '' assert_arg(1,delim,'string') - return concat(imap(tostring,self),delim) + return concat(array_tostring(self),delim) end --- join a list of strings.
--- Uses table.concat directly. --- @class function --- @name List:concat --- @param delim a delimiter +-- Uses `table.concat` directly. +-- @function List:concat +-- @string[opt=''] delim a delimiter -- @return a string List.concat = concat @@ -402,73 +373,50 @@ local function tostring_q(val) end --- how our list should be rendered as a string. Uses join(). +-- @within metamethods -- @see List:join function List:__tostring() return '{'..self:join(',',tostring_q)..'}' end ---[[ --- NOTE: this works, but is unreliable. If you leave the loop before finishing, --- then the iterator is not reset. ---- can iterate over a list directly. --- @usage for v in ls do print(v) end -function List:__call() - if not self.key then self.key = 1 end - local value = self[self.key] - self.key = self.key + 1 - if not value then self.key = nil end - return value -end ---]] - ---[[ -function List.__call(t,v,i) - i = (i or 0) + 1 - v = t[i] - if v then return i, v end -end ---]] - -local MethodIter = {} - -function MethodIter:__index (name) - return function(mm,...) - return self.list:foreachm(name,...) - end -end - ---- call the function for each element of the list. --- @param fun a function or callable object +--- call the function on each element of the list. +-- @func fun a function or callable object -- @param ... optional values to pass to function function List:foreach (fun,...) - if fun==nil then - return setmetatable({list=self},MethodIter) - end fun = function_arg(1,fun) for i = 1,#self do fun(self[i],...) end end +local function lookup_fun (obj,name) + local f = obj[name] + if not f then error(type(obj).." does not have method "..name,3) end + return f +end + +--- call the named method on each element of the list. +-- @string name the method name +-- @param ... optional values to pass to function function List:foreachm (name,...) for i = 1,#self do local obj = self[i] - local f = assert(obj[name],"method not found on object") + local f = lookup_fun(obj,name) f(obj,...) end end --- create a list of all elements which match a function. --- @param fun a boolean function --- @param arg optional argument to be passed as second argument of the predicate +-- @func fun a boolean function +-- @param[opt] arg optional argument to be passed as second argument of the predicate -- @return a new filtered list. function List:filter (fun,arg) return makelist(filter(self,fun,arg),self) end --- split a string using a delimiter. --- @param s the string --- @param delim the delimiter (default spaces) +-- @string s the string +-- @string[opt] delim the delimiter (default spaces) -- @return a List of strings -- @see pl.utils.split function List.split (s,delim) @@ -476,34 +424,20 @@ function List.split (s,delim) return makelist(split(s,delim)) end -local MethodMapper = {} - -function MethodMapper:__index (name) - return function(mm,...) - return self.list:mapm(name,...) - end -end - --- apply a function to all elements. --- Any extra arguments will be passed to the function; if the function --- is `nil` then `map` returns a mapper object that maps over a method --- of the items --- @param fun a function of at least one argument +-- Any extra arguments will be passed to the function. +-- @func fun a function of at least one argument -- @param ... arbitrary extra arguments. -- @return a new list: {f(x) for x in self} -- @usage List{'one','two'}:map(string.upper) == {'ONE','TWO'} --- @usage List{'one','two'}:map():sub(1,2) == {'on','tw'} -- @see pl.tablex.imap function List:map (fun,...) - if fun==nil then - return setmetatable({list=self},MethodMapper) - end return makelist(imap(fun,self,...),self) end --- apply a function to all elements, in-place. -- Any extra arguments are passed to the function. --- @param fun A function that takes at least one argument +-- @func fun A function that takes at least one argument -- @param ... arbitrary extra arguments. -- @return the list. function List:transform (fun,...) @@ -513,8 +447,8 @@ end --- apply a function to elements of two lists. -- Any extra arguments will be passed to the function --- @param fun a function of at least two arguments --- @param ls another list +-- @func fun a function of at least two arguments +-- @tparam List ls another list -- @param ... arbitrary extra arguments. -- @return a new list: {f(x,y) for x in self, for x in arg1} -- @see pl.tablex.imap2 @@ -524,24 +458,44 @@ end --- apply a named method to all elements. -- Any extra arguments will be passed to the method. --- @param name name of method +-- @string name name of method -- @param ... extra arguments -- @return a new list of the results -- @see pl.seq.mapmethod function List:mapm (name,...) local res = {} - local t = self - for i = 1,#t do - local val = t[i] - local fn = val[name] - if not fn then error(type(val).." does not have method "..name,2) end + for i = 1,#self do + local val = self[i] + local fn = lookup_fun(val,name) res[i] = fn(val,...) end return makelist(res,self) end +local function composite_call (method,f) + return function(self,...) + return self[method](self,f,...) + end +end + +function List.default_map_with(T) + return function(self,name) + local m + if T then + local f = lookup_fun(T,name) + m = composite_call('map',f) + else + m = composite_call('mapn',name) + end + getmetatable(self)[name] = m -- and cache.. + return m + end +end + +List.default_map = List.default_map_with + --- 'reduce' a list using a binary function. --- @param fun a function of two arguments +-- @func fun a function of two arguments -- @return result of the function -- @see pl.tablex.reduce function List:reduce (fun) @@ -550,10 +504,10 @@ end --- partition a list using a classifier function. -- The function may return nil, but this will be converted to the string key '
--- if it s a table of the form
{{key1=val1},{key2=val2},...}
these will be appended. +-- If the table is itself an OrderedMap, then its entries will be appended. +-- if it s a table of the form `{{key1=val1},{key2=val2},...}` these will be appended. +-- -- Otherwise, it is assumed to be a map-like table, and order of extra entries is arbitrary. --- @param t a table. +-- @tab t a table. -- @return the map, or nil in case of error -- @return the error message function OrderedMap:update (t) @@ -60,26 +63,34 @@ function OrderedMap:update (t) return self end ---- set the key's value. This key will be appended at the end of the map.
+--- set the key's value. This key will be appended at the end of the map. +-- -- If the value is nil, then the key is removed. -- @param key the key -- @param val the value -- @return the map function OrderedMap:set (key,val) - if not self[key] and val ~= nil then -- ensure that keys are unique - self._keys:append(key) - elseif val == nil then -- removing a key-value pair - self._keys:remove_value(key) + if self[key] == nil and val ~= nil then -- new key + self._keys:append(key) -- we keep in order + rawset(self,key,val) -- don't want to provoke __newindex! + else -- existing key-value pair + if val == nil then + self._keys:remove_value(key) + rawset(self,key,nil) + else + self[key] = val + end end - self[key] = val return self end +OrderedMap.__newindex = OrderedMap.set + --- insert a key/value pair before a given position. -- Note: if the map already contains the key, then this effectively -- moves the item to the new position by first removing at the old position. -- Has no effect if the key does not exist and val is nil --- @param pos a position starting at 1 +-- @int pos a position starting at 1 -- @param key the key -- @param val the value; if nil use the old value function OrderedMap:insert (pos,key,val) @@ -90,7 +101,7 @@ function OrderedMap:insert (pos,key,val) end if val then self._keys:insert(pos,key) - self[key] = val + rawset(self,key,val) end return self end @@ -110,7 +121,7 @@ function OrderedMap:values () end --- sort the keys. --- @param cmp a comparison function as for @{table.sort} +-- @func cmp a comparison function as for @{table.sort} -- @return the map function OrderedMap:sort (cmp) tsort(self._keys,cmp) @@ -130,8 +141,13 @@ function OrderedMap:iter () end end +--- iterate over an ordered map (5.2). +-- @within metamethods +-- @function OrderedMap:__pairs OrderedMap.__pairs = OrderedMap.iter +--- string representation of an ordered map. +-- @within metamethods function OrderedMap:__tostring () local res = {} for i,v in ipairs(self._keys) do diff --git a/files/lua/pl/Set.lua b/files/lua/pl/Set.lua index a09415d..8626f44 100644 --- a/files/lua/pl/Set.lua +++ b/files/lua/pl/Set.lua @@ -16,17 +16,17 @@ -- > = fruit*colours -- [orange] -- --- Depdencies: `pl.utils`, `pl.tablex`, `pl.class` +-- Depdencies: `pl.utils`, `pl.tablex`, `pl.class`, (`pl.List` if __tostring is used) -- @module pl.Set local tablex = require 'pl.tablex' local utils = require 'pl.utils' -local stdmt = utils.stdmt +local array_tostring, concat = utils.array_tostring, table.concat local tmakeset,deepcompare,merge,keys,difference,tupdate = tablex.makeset,tablex.deepcompare,tablex.merge,tablex.keys,tablex.difference,tablex.update local Map = require 'pl.Map' -local Set = stdmt.Set -local List = stdmt.List local class = require 'pl.class' +local stdmt = utils.stdmt +local Set = stdmt.Set -- the Set class -------------------- class(Map,nil,Set) @@ -43,6 +43,7 @@ end -- @class function -- @name Set function Set:_init (t) + t = t or {} local mt = getmetatable(t) if mt == Set or mt == Map then for k in pairs(t) do self[k] = true end @@ -51,13 +52,16 @@ function Set:_init (t) end end +--- string representation of a set. +-- @within metamethods function Set:__tostring () - return '['..Set.values(self):join ','..']' + return '['..concat(array_tostring(Set.values(self)),',')..']' end --- get a list of the values in a set. -- @param self a Set -- @function Set.values +-- @return a list Set.values = Map.keys --- map a function over the values of a set. @@ -81,15 +85,33 @@ end function Set.union (self,set) return merge(self,set,true) end + +--- union of sets. +-- @within metamethods +-- @function Set.__add Set.__add = Set.union --- intersection of two sets (also *). -- @param self a Set -- @param set another set -- @return a new set +-- @usage +-- > s = Set{10,20,30} +-- > t = Set{20,30,40} +-- > = t +-- [20,30,40] +-- > = Set.intersection(s,t) +-- [30,20] +-- > = s*t +-- [30,20] + function Set.intersection (self,set) return merge(self,set,false) end + +--- intersection of sets. +-- @within metamethods +-- @function Set.__mul Set.__mul = Set.intersection --- new set with elements in the set that are not in the other (also -). @@ -99,6 +121,11 @@ Set.__mul = Set.intersection function Set.difference (self,set) return difference(self,set,false) end + + +--- difference of sets. +-- @within metamethods +-- @function Set.__sub Set.__sub = Set.difference -- a new set with elements in _either_ the set _or_ other but not both (also ^). @@ -108,6 +135,10 @@ Set.__sub = Set.difference function Set.symmetric_difference (self,set) return difference(self,set,true) end + +--- symmetric difference of sets. +-- @within metamethods +-- @function Set.__pow Set.__pow = Set.symmetric_difference --- is the first set a subset of the second (also <)?. @@ -120,12 +151,16 @@ function Set.issubset (self,set) end return true end -Set.__lt = Set.subset + +--- first set subset of second? +-- @within metamethods +-- @function Set.__lt +Set.__lt = Set.issubset --- is the set empty?. -- @param self a Set -- @return true or false -function Set.issempty (self) +function Set.isempty (self) return next(self) == nil end @@ -144,8 +179,13 @@ end -- @function Set.len Set.len = tablex.size +--- cardinality of set (5.2). +-- @within metamethods +-- @function Set.__len Set.__len = Set.len +--- equality between sets. +-- @within metamethods function Set.__eq (s1,s2) return Set.issubset(s1,s2) and Set.issubset(s2,s1) end diff --git a/files/lua/pl/app.lua b/files/lua/pl/app.lua index 4c5f1fd..ca501ee 100644 --- a/files/lua/pl/app.lua +++ b/files/lua/pl/app.lua @@ -1,14 +1,12 @@ --- Application support functions. -- See @{01-introduction.md.Application_Support|the Guide} -- --- Dependencies: `pl.utils`, `pl.path`, `lfs` +-- Dependencies: `pl.utils`, `pl.path` -- @module pl.app local io,package,require = _G.io, _G.package, _G.require local utils = require 'pl.utils' local path = require 'pl.path' -local lfs = require 'lfs' - local app = {} @@ -23,12 +21,12 @@ end -- `base` allows these modules to be put in a specified subdirectory, to allow for -- cleaner deployment and resolve potential conflicts between a script name and its -- library directory. --- @param base optional base directory. --- @return the current script's path with a trailing slash +-- @string base optional base directory. +-- @treturn string the current script's path with a trailing slash function app.require_here (base) local p = path.dirname(check_script_name()) if not path.isabs(p) then - p = path.join(lfs.currentdir(),p) + p = path.join(path.currentdir(),p) end if p:sub(-1,-1) ~= path.sep then p = p..path.sep @@ -47,7 +45,7 @@ end --- return a suitable path for files private to this application. -- These will look like '~/.SNAME/file', with '~' as with expanduser and -- SNAME is the name of the script without .lua extension. --- @param file a filename (w/out path) +-- @string file a filename (w/out path) -- @return a full pathname, or nil -- @return 'cannot create' error function app.appfile (file) @@ -55,7 +53,7 @@ function app.appfile (file) local name,ext = path.splitext(sname) local dir = path.join(path.expanduser('~'),'.'..name) if not path.isdir(dir) then - local ret = lfs.mkdir(dir) + local ret = path.mkdir(dir) if not ret then return utils.raise ('cannot create '..dir) end end return path.join(dir,file) @@ -75,8 +73,8 @@ function app.platform() end end ---- return the full command-line used to invoke this script --- any extra flags occupy slots, so that 'lua -lpl' gives us {[-2]='lua',[-1]='-lpl') +--- return the full command-line used to invoke this script. +-- Any extra flags occupy slots, so that `lua -lpl` gives us `{[-2]='lua',[-1]='-lpl'}` -- @return command-line -- @return name of Lua program used function app.lua () @@ -97,12 +95,12 @@ function app.lua () end --- parse command-line arguments into flags and parameters. --- Understands GNU-style command-line flags; short (-f) and long (--flag). --- These may be given a value with either '=' or ':' (-k:2,--alpha=3.2,-n2); +-- Understands GNU-style command-line flags; short (`-f`) and long (`--flag`). +-- These may be given a value with either '=' or ':' (`-k:2`,`--alpha=3.2`,`-n2`); -- note that a number value can be given without a space. --- Multiple short args can be combined like so: (-abcd). --- @param args an array of strings (default is the global 'arg') --- @param flags_with_values any flags that take values, e.g.
{out=true}
+-- Multiple short args can be combined like so: ( `-abcd`).
+-- @tparam {string} args an array of strings (default is the global `arg`)
+-- @tab flags_with_values any flags that take values, e.g. `{out=true}`
-- @return a table of flags (flag=value pairs)
-- @return an array of parameters
-- @raise if args is nil, then the global `args` must be available!
@@ -131,8 +129,8 @@ function app.parse_args (args,flags_with_values)
flags[v] = args[i+1]
i = i + 1
else
- -- a value can be indicated with = or :
- local var,val = utils.splitv (v,'[=:]')
+ -- a value can also be indicated with =
+ local var,val = utils.splitv (v,'=')
var = var or v
val = val or true
if not is_long then
diff --git a/files/lua/pl/array2d.lua b/files/lua/pl/array2d.lua
index 7fde031..e3cf7e4 100644
--- a/files/lua/pl/array2d.lua
+++ b/files/lua/pl/array2d.lua
@@ -1,16 +1,16 @@
--- Operations on two-dimensional arrays.
-- See @{02-arrays.md.Operations_on_two_dimensional_tables|The Guide}
--
--- Dependencies: `pl.utils`, `pl.tablex`
+-- Dependencies: `pl.utils`, `pl.tablex`, `pl.types`
-- @module pl.array2d
-local require, type,tonumber,assert,tostring,io,ipairs,string,table =
- _G.require, _G.type,_G.tonumber,_G.assert,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table
+local type,tonumber,assert,tostring,io,ipairs,string,table =
+ _G.type,_G.tonumber,_G.assert,_G.tostring,_G.io,_G.ipairs,_G.string,_G.table
local setmetatable,getmetatable = setmetatable,getmetatable
local tablex = require 'pl.tablex'
local utils = require 'pl.utils'
-
+local types = require 'pl.types'
local imap,tmap,reduce,keys,tmap2,tset,index_by = tablex.imap,tablex.map,tablex.reduce,tablex.keys,tablex.map2,tablex.set,tablex.index_by
local remove = table.remove
local splitv,fprintf,assert_arg = utils.splitv,utils.fprintf,utils.assert_arg
@@ -37,16 +37,16 @@ local function index (t,k)
end
--- return the row and column size.
--- @param t a 2d array
--- @return number of rows
--- @return number of cols
+-- @array2d t a 2d array
+-- @treturn int number of rows
+-- @treturn int number of cols
function array2d.size (t)
assert_arg(1,t,'table')
return #t,#t[1]
end
--- extract a column from the 2D array.
--- @param a 2d array
+-- @array2d a 2d array
-- @param key an index or key
-- @return 1d array
function array2d.column (a,key)
@@ -56,8 +56,8 @@ end
local column = array2d.column
--- map a function over a 2D array
--- @param f a function of at least one argument
--- @param a 2d array
+-- @func f a function of at least one argument
+-- @array2d a 2d array
-- @param arg an optional extra argument to be passed to the function.
-- @return 2d array
function array2d.map (f,a,arg)
@@ -67,8 +67,8 @@ function array2d.map (f,a,arg)
end
--- reduce the rows using a function.
--- @param f a binary function
--- @param a 2d array
+-- @func f a binary function
+-- @array2d a 2d array
-- @return 1d array
-- @see pl.tablex.reduce
function array2d.reduce_rows (f,a)
@@ -77,8 +77,8 @@ function array2d.reduce_rows (f,a)
end
--- reduce the columns using a function.
--- @param f a binary function
--- @param a 2d array
+-- @func f a binary function
+-- @array2d a 2d array
-- @return 1d array
-- @see pl.tablex.reduce
function array2d.reduce_cols (f,a)
@@ -87,8 +87,8 @@ function array2d.reduce_cols (f,a)
end
--- reduce a 2D array into a scalar, using two operations.
--- @param opc operation to reduce the final result
--- @param opr operation to reduce the rows
+-- @func opc operation to reduce the final result
+-- @func opr operation to reduce the rows
-- @param a 2D array
function array2d.reduce2 (opc,opr,a)
assert_arg(3,a,'table')
@@ -102,18 +102,17 @@ end
--- map a function over two arrays.
-- They can be both or either 2D arrays
--- @param f function of at least two arguments
--- @param ad order of first array
--- @param bd order of second array
--- @param a 1d or 2d array
--- @param b 1d or 2d array
+-- @func f function of at least two arguments
+-- @int ad order of first array (1 or 2)
+-- @int bd order of second array (1 or 2)
+-- @tab a 1d or 2d array
+-- @tab b 1d or 2d array
-- @param arg optional extra argument to pass to function
-- @return 2D array, unless both arrays are 1D
function array2d.map2 (f,ad,bd,a,b,arg)
assert_arg(1,a,'table')
assert_arg(2,b,'table')
f = utils.function_arg(1,f)
- --local ad,bd = dimension(a),dimension(b)
if ad == 1 and bd == 2 then
return imap(function(row)
return tmap2(f,a,row,arg)
@@ -132,9 +131,9 @@ function array2d.map2 (f,ad,bd,a,b,arg)
end
--- cartesian product of two 1d arrays.
--- @param f a function of 2 arguments
--- @param t1 a 1d table
--- @param t2 a 1d table
+-- @func f a function of 2 arguments
+-- @array t1 a 1d table
+-- @array t2 a 1d table
-- @return 2d table
-- @usage product('..',{1,2},{'a','b'}) == {{'1a','2a'},{'1b','2b'}}
function array2d.product (f,t1,t2)
@@ -150,7 +149,7 @@ end
--- flatten a 2D array.
-- (this goes over columns first.)
--- @param t 2d table
+-- @array2d t 2d table
-- @return a 1d table
-- @usage flatten {{1,2},{3,4},{5,6}} == {1,2,3,4,5,6}
function array2d.flatten (t)
@@ -166,9 +165,9 @@ function array2d.flatten (t)
end
--- reshape a 2D array.
--- @param t 2d array
--- @param nrows new number of rows
--- @param co column-order (Fortran-style) (default false)
+-- @array2d t 2d array
+-- @int nrows new number of rows
+-- @bool co column-order (Fortran-style) (default false)
-- @return a new 2d array
function array2d.reshape (t,nrows,co)
local nr,nc = array2d.size(t)
@@ -199,18 +198,18 @@ function array2d.reshape (t,nrows,co)
end
--- swap two rows of an array.
--- @param t a 2d array
--- @param i1 a row index
--- @param i2 a row index
+-- @array2d t a 2d array
+-- @int i1 a row index
+-- @int i2 a row index
function array2d.swap_rows (t,i1,i2)
assert_arg(1,t,'table')
t[i1],t[i2] = t[i2],t[i1]
end
--- swap two columns of an array.
--- @param t a 2d array
--- @param j1 a column index
--- @param j2 a column index
+-- @array2d t a 2d array
+-- @int j1 a column index
+-- @int j2 a column index
function array2d.swap_cols (t,j1,j2)
assert_arg(1,t,'table')
for i = 1,#t do
@@ -220,15 +219,15 @@ function array2d.swap_cols (t,j1,j2)
end
--- extract the specified rows.
--- @param t 2d array
--- @param ridx a table of row indices
+-- @array2d t 2d array
+-- @tparam {int} ridx a table of row indices
function array2d.extract_rows (t,ridx)
return obj(t,index_by(t,ridx))
end
--- extract the specified columns.
--- @param t 2d array
--- @param cidx a table of column indices
+-- @array2d t 2d array
+-- @tparam {int} cidx a table of column indices
function array2d.extract_cols (t,cidx)
assert_arg(1,t,'table')
local res = {}
@@ -239,15 +238,14 @@ function array2d.extract_cols (t,cidx)
end
--- remove a row from an array.
--- @class function
--- @name array2d.remove_row
--- @param t a 2d array
--- @param i a row index
+-- @function array2d.remove_row
+-- @array2d t a 2d array
+-- @int i a row index
array2d.remove_row = remove
--- remove a column from an array.
--- @param t a 2d array
--- @param j a column index
+-- @array2d t a 2d array
+-- @int j a column index
function array2d.remove_col (t,j)
assert_arg(1,t,'table')
for i = 1,#t do
@@ -274,11 +272,11 @@ end
--- parse a spreadsheet range.
-- The range can be specified either as 'A1:B2' or 'R1C1:R2C2';
-- a special case is a single element (e.g 'A1' or 'R1C1')
--- @param s a range.
--- @return start col
--- @return start row
--- @return end col
--- @return end row
+-- @string s a range.
+-- @treturn int start col
+-- @treturn int start row
+-- @treturn int end col
+-- @treturn int end row
function array2d.parse_range (s)
if s:find ':' then
local start,finish = splitv(s,':')
@@ -292,8 +290,8 @@ function array2d.parse_range (s)
end
--- get a slice of a 2D array using spreadsheet range notation. @see parse_range
--- @param t a 2D array
--- @param rstr range expression
+-- @array2d t a 2D array
+-- @string rstr range expression
-- @return a slice
-- @see array2d.parse_range
-- @see array2d.slice
@@ -318,11 +316,11 @@ end
--- get a slice of a 2D array. Note that if the specified range has
-- a 1D result, the rank of the result will be 1.
--- @param t a 2D array
--- @param i1 start row (default 1)
--- @param j1 start col (default 1)
--- @param i2 end row (default N)
--- @param j2 end col (default M)
+-- @array2d t a 2D array
+-- @int i1 start row (default 1)
+-- @int j1 start col (default 1)
+-- @int i2 end row (default N)
+-- @int j2 end col (default M)
-- @return an array, 2D in general but 1D in special cases.
function array2d.slice (t,i1,j1,i2,j2)
assert_arg(1,t,'table')
@@ -346,12 +344,12 @@ function array2d.slice (t,i1,j1,i2,j2)
end
--- set a specified range of an array to a value.
--- @param t a 2D array
+-- @array2d t a 2D array
-- @param value the value (may be a function)
--- @param i1 start row (default 1)
--- @param j1 start col (default 1)
--- @param i2 end row (default N)
--- @param j2 end col (default M)
+-- @int i1 start row (default 1)
+-- @int j1 start col (default 1)
+-- @int i2 end row (default N)
+-- @int j2 end col (default M)
-- @see tablex.set
function array2d.set (t,value,i1,j1,i2,j2)
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
@@ -361,13 +359,13 @@ function array2d.set (t,value,i1,j1,i2,j2)
end
--- write a 2D array to a file.
--- @param t a 2D array
+-- @array2d t a 2D array
-- @param f a file object (default stdout)
--- @param fmt a format string (default is just to use tostring)
--- @param i1 start row (default 1)
--- @param j1 start col (default 1)
--- @param i2 end row (default N)
--- @param j2 end col (default M)
+-- @string fmt a format string (default is just to use tostring)
+-- @int i1 start row (default 1)
+-- @int j1 start col (default 1)
+-- @int i2 end row (default N)
+-- @int j2 end col (default M)
function array2d.write (t,f,fmt,i1,j1,i2,j2)
assert_arg(1,t,'table')
f = f or stdout
@@ -384,13 +382,13 @@ function array2d.write (t,f,fmt,i1,j1,i2,j2)
end
--- perform an operation for all values in a 2D array.
--- @param t 2D array
--- @param row_op function to call on each value
--- @param end_row_op function to call at end of each row
--- @param i1 start row (default 1)
--- @param j1 start col (default 1)
--- @param i2 end row (default N)
--- @param j2 end col (default M)
+-- @array2d t 2D array
+-- @func row_op function to call on each value
+-- @func end_row_op function to call at end of each row
+-- @int i1 start row (default 1)
+-- @int j1 start col (default 1)
+-- @int i2 end row (default N)
+-- @int j2 end col (default M)
function array2d.forall (t,row_op,end_row_op,i1,j1,i2,j2)
assert_arg(1,t,'table')
i1,j1,i2,j2 = default_range(t,i1,j1,i2,j2)
@@ -406,14 +404,14 @@ end
local min, max = math.min, math.max
---- move a block from the destination to the source.
--- @param dest a 2D array
--- @param di start row in dest
--- @param dj start col in dest
--- @param src a 2D array
--- @param i1 start row (default 1)
--- @param j1 start col (default 1)
--- @param i2 end row (default N)
--- @param j2 end col (default M)
+-- @array2d dest a 2D array
+-- @int di start row in dest
+-- @int dj start col in dest
+-- @array2d src a 2D array
+-- @int i1 start row (default 1)
+-- @int j1 start col (default 1)
+-- @int i2 end row (default N)
+-- @int j2 end col (default M)
function array2d.move (dest,di,dj,src,i1,j1,i2,j2)
assert_arg(1,dest,'table')
assert_arg(4,src,'table')
@@ -431,12 +429,12 @@ function array2d.move (dest,di,dj,src,i1,j1,i2,j2)
end
--- iterate over all elements in a 2D array, with optional indices.
--- @param a 2D array
--- @param indices with indices (default false)
--- @param i1 start row (default 1)
--- @param j1 start col (default 1)
--- @param i2 end row (default N)
--- @param j2 end col (default M)
+-- @array2d a 2D array
+-- @tparam {int} indices with indices (default false)
+-- @int i1 start row (default 1)
+-- @int j1 start col (default 1)
+-- @int i2 end row (default N)
+-- @int j2 end col (default M)
-- @return either value or i,j,value depending on indices
function array2d.iter (a,indices,i1,j1,i2,j2)
assert_arg(1,a,'table')
@@ -463,7 +461,7 @@ function array2d.iter (a,indices,i1,j1,i2,j2)
end
--- iterate over all columns.
--- @param a a 2D array
+-- @array2d a a 2D array
-- @return each column in turn
function array2d.columns (a)
assert_arg(1,a,'table')
@@ -477,13 +475,13 @@ function array2d.columns (a)
end
--- new array of specified dimensions
--- @param rows number of rows
--- @param cols number of cols
+-- @int rows number of rows
+-- @int cols number of cols
-- @param val initial value; if it's a function then use `val(i,j)`
-- @return new 2d array
function array2d.new(rows,cols,val)
local res = {}
- local fun = utils.is_callable(val)
+ local fun = types.is_callable(val)
for i = 1,rows do
local row = {}
if fun then
diff --git a/files/lua/pl/class.lua b/files/lua/pl/class.lua
index e482738..d260c31 100644
--- a/files/lua/pl/class.lua
+++ b/files/lua/pl/class.lua
@@ -4,13 +4,16 @@
-- B = class(A)
-- class.B(A)
--
--- The latter form creates a named class.
+-- The latter form creates a named class within the current environment. Note
+-- that this implicitly brings in `pl.utils` as a dependency.
--
-- See the Guide for further @{01-introduction.md.Simplifying_Object_Oriented_Programming_in_Lua|discussion}
-- @module pl.class
local error, getmetatable, io, pairs, rawget, rawset, setmetatable, tostring, type =
_G.error, _G.getmetatable, _G.io, _G.pairs, _G.rawget, _G.rawset, _G.setmetatable, _G.tostring, _G.type
+local compat
+
-- this trickery is necessary to prevent the inheritance of 'super' and
-- the resulting recursive call problems.
local function call_ctor (c,obj,...)
@@ -18,17 +21,43 @@ local function call_ctor (c,obj,...)
local base = rawget(c,'_base')
if base then
local parent_ctor = rawget(base,'_init')
+ while not parent_ctor do
+ base = rawget(base,'_base')
+ if not base then break end
+ parent_ctor = rawget(base,'_init')
+ end
if parent_ctor then
- obj.super = function(obj,...)
+ rawset(obj,'super',function(obj,...)
call_ctor(base,obj,...)
- end
+ end)
end
end
local res = c._init(obj,...)
- obj.super = nil
+ rawset(obj,'super',nil)
return res
end
+--- initializes an __instance__ upon creation.
+-- @function class:_init
+-- @param ... parameters passed to the constructor
+-- @usage local Cat = class()
+-- function Cat:_init(name)
+-- --self:super(name) -- call the ancestor initializer if needed
+-- self.name = name
+-- end
+--
+-- local pussycat = Cat("pussycat")
+-- print(pussycat.name) --> pussycat
+
+--- checks whether an __instance__ is derived from some class.
+-- Works the other way around as `class_of`.
+-- @function instance:is_a
+-- @param some_class class to check against
+-- @return `true` if `instance` is derived from `some_class`
+-- @usage local pussycat = Lion() -- assuming Lion derives from Cat
+-- if pussycat:is_a(Cat) then
+-- -- it's true
+-- end
local function is_a(self,klass)
local m = getmetatable(self)
if not m then return false end --*can't be an object!
@@ -39,11 +68,29 @@ local function is_a(self,klass)
return false
end
+--- checks whether an __instance__ is derived from some class.
+-- Works the other way around as `is_a`.
+-- @function some_class:class_of
+-- @param some_instance instance to check against
+-- @return `true` if `some_instance` is derived from `some_class`
+-- @usage local pussycat = Lion() -- assuming Lion derives from Cat
+-- if Cat:class_of(pussycat) then
+-- -- it's true
+-- end
local function class_of(klass,obj)
if type(klass) ~= 'table' or not rawget(klass,'is_a') then return false end
return klass.is_a(obj,klass)
end
+--- cast an object to another class.
+-- It is not clever (or safe!) so use carefully.
+-- @param some_instance the object to be changed
+-- @function some_class:cast
+local function cast (klass, obj)
+ return setmetatable(obj,klass)
+end
+
+
local function _class_tostring (obj)
local mt = obj._class
local name = rawget(mt,'_name')
@@ -54,21 +101,31 @@ local function _class_tostring (obj)
return str
end
-local function tupdate(td,ts)
+local function tupdate(td,ts,dont_override)
for k,v in pairs(ts) do
- td[k] = v
+ if not dont_override or td[k] == nil then
+ td[k] = v
+ end
end
end
local function _class(base,c_arg,c)
- c = c or {} -- a new class instance, which is the metatable for all objects of this type
- -- the class will be the metatable for all its objects,
+ -- the class `c` will be the metatable for all its objects,
-- and they will look up their methods in it.
- local mt = {} -- a metatable for the class instance
-
+ local mt = {} -- a metatable for the class to support __call and _handler
+ -- can define class by passing it a plain table of methods
+ local plain = type(base) == 'table' and not getmetatable(base)
+ if plain then
+ c = base
+ base = c._base
+ else
+ c = c or {}
+ end
+
if type(base) == 'table' then
-- our new class is a shallow copy of the base class!
- tupdate(c,base)
+ -- but be careful not to wipe out any methods we have been given at this point!
+ tupdate(c,base,plain)
c._base = base
-- inherit the 'not found' handler, if present
if rawget(c,'_handler') then mt.__index = c._handler end
@@ -78,7 +135,9 @@ local function _class(base,c_arg,c)
c.__index = c
setmetatable(c,mt)
- c._init = nil
+ if not plain then
+ c._init = nil
+ end
if base and rawget(base,'_class_init') then
base._class_init(c,c_arg)
@@ -86,7 +145,9 @@ local function _class(base,c_arg,c)
-- expose a ctor which can be called by io.open
+-- @string file filename
+-- @string[opt] opt same as second parameter of `io.open`
function lapp.open (file,opt)
local val,err = io.open(file,opt)
if not val then lapp.error(err,true) end
@@ -90,8 +90,8 @@ function lapp.open (file,opt)
end
--- quit if the condition is false.
--- @param condn a condition
--- @param msg an optional message
+-- @bool condn a condition
+-- @string msg message text
function lapp.assert(condn,msg)
if not condn then
lapp.error(msg)
@@ -108,7 +108,7 @@ local function xtonumber(s)
return val
end
-local types
+local types = {}
local builtin_types = {string=true,number=true,['file-in']='file',['file-out']='file',boolean=true}
@@ -121,7 +121,7 @@ local function convert_parameter(ps,val)
elseif builtin_types[ps.type] == 'file' then
val = lapp.open(val,(ps.type == 'file-in' and 'r') or 'w' )
elseif ps.type == 'boolean' then
- val = true
+ return val
end
if ps.constraint then
ps.constraint(val)
@@ -131,9 +131,9 @@ end
--- add a new type to Lapp. These appear in parens after the value like
-- a range constraint, e.g. '+--- get current line number. -- Only available if the input source is a file-like object. -- @param tok a token stream -- @return the line number and current column @@ -260,7 +267,7 @@ function lexer.getrest (tok) end --- get the Lua keywords as a set-like table. --- So
res["and"]
etc would be true
.
+-- So `res["and"]` etc would be `true`.
-- @return a table
function lexer.get_keywords ()
if not lua_keyword then
@@ -277,12 +284,11 @@ function lexer.get_keywords ()
return lua_keyword
end
-
--- create a Lua token iterator from a string or file-like object.
-- Will return the token type and value.
--- @param s the string
--- @param filter a table of token types to exclude, by default {space=true,comments=true}
--- @param options a table of options; by default, {number=true,string=true},
+-- @string s the string
+-- @tab[opt] filter a table of token types to exclude, by default `{space=true,comments=true}`
+-- @tab[opt] options a table of options; by default, `{number=true,string=true}`,
-- which means convert numbers and strip string quotes.
function lexer.lua(s,filter,options)
filter = filter or {space=true,comments=true}
@@ -297,9 +303,9 @@ function lexer.lua(s,filter,options)
{STRING3,sdump},
{STRING0,sdump},
{STRING1,sdump},
- {'^%-%-%[%[.-%]%]',cdump},
+ {'^%-%-%[(=*)%[.-%]%1%]',cdump},
{'^%-%-.-\n',cdump},
- {'^%[%[.-%]%]',sdump_l},
+ {'^%[(=*)%[.-%]%1%]',sdump_l},
{'^==',tdump},
{'^~=',tdump},
{'^<=',tdump},
@@ -314,9 +320,9 @@ end
--- create a C/C++ token iterator from a string or file-like object.
-- Will return the token type type and value.
--- @param s the string
--- @param filter a table of token types to exclude, by default {space=true,comments=true}
--- @param options a table of options; by default, {number=true,string=true},
+-- @string s the string
+-- @tab[opt] filter a table of token types to exclude, by default `{space=true,comments=true}`
+-- @tab[opt] options a table of options; by default, `{number=true,string=true}`,
-- which means convert numbers and strip string quotes.
function lexer.cpp(s,filter,options)
filter = filter or {comments=true}
@@ -372,8 +378,8 @@ end
--- get a list of parameters separated by a delimiter from a stream.
-- @param tok the token stream
--- @param endtoken end of list (default ')'). Can be '\n'
--- @param delim separator (default ',')
+-- @string[opt=')'] endtoken end of list. Can be '\n'
+-- @string[opt=','] delim separator
-- @return a list of token lists.
function lexer.get_separated_list(tok,endtoken,delim)
endtoken = endtoken or ')'
@@ -438,8 +444,8 @@ local skipws = lexer.skipws
--- get the next token, which must be of the expected type.
-- Throws an error if this type does not match!
-- @param tok the token stream
--- @param expected_type the token type
--- @param no_skip_ws whether we should skip whitespace
+-- @string expected_type the token type
+-- @bool no_skip_ws whether we should skip whitespace
function lexer.expecting (tok,expected_type,no_skip_ws)
assert_arg(1,tok,'function')
assert_arg(2,expected_type,'string')
diff --git a/files/lua/pl/operator.lua b/files/lua/pl/operator.lua
index ba77d80..f857107 100644
--- a/files/lua/pl/operator.lua
+++ b/files/lua/pl/operator.lua
@@ -16,152 +16,151 @@ local utils = require 'pl.utils'
local operator = {}
---- apply function to some arguments ()
+--- apply function to some arguments **()**
-- @param fn a function or callable object
-- @param ... arguments
function operator.call(fn,...)
return fn(...)
end
---- get the indexed value from a table []
+--- get the indexed value from a table **[]**
-- @param t a table or any indexable object
-- @param k the key
function operator.index(t,k)
return t[k]
end
---- returns true if arguments are equal ==
+--- returns true if arguments are equal **==**
-- @param a value
-- @param b value
function operator.eq(a,b)
return a==b
end
---- returns true if arguments are not equal ~=
+--- returns true if arguments are not equal **~=**
-- @param a value
-- @param b value
function operator.neq(a,b)
return a~=b
end
---- returns true if a is less than b <
+--- returns true if a is less than b **<**
-- @param a value
-- @param b value
function operator.lt(a,b)
return a < b
end
---- returns true if a is less or equal to b <=
+--- returns true if a is less or equal to b **<=**
-- @param a value
-- @param b value
function operator.le(a,b)
return a <= b
end
---- returns true if a is greater than b >
+--- returns true if a is greater than b **>**
-- @param a value
-- @param b value
function operator.gt(a,b)
return a > b
end
---- returns true if a is greater or equal to b >=
+--- returns true if a is greater or equal to b **>=**
-- @param a value
-- @param b value
function operator.ge(a,b)
return a >= b
end
---- returns length of string or table #
+--- returns length of string or table **#**
-- @param a a string or a table
function operator.len(a)
return #a
end
---- add two values +
+--- add two values **+**
-- @param a value
-- @param b value
function operator.add(a,b)
return a+b
end
---- subtract b from a -
+--- subtract b from a **-**
-- @param a value
-- @param b value
function operator.sub(a,b)
return a-b
end
---- multiply two values *
+--- multiply two values __*__
-- @param a value
-- @param b value
function operator.mul(a,b)
return a*b
end
---- divide first value by second /
+--- divide first value by second **/**
-- @param a value
-- @param b value
function operator.div(a,b)
return a/b
end
---- raise first to the power of second ^
+--- raise first to the power of second **^**
-- @param a value
-- @param b value
function operator.pow(a,b)
return a^b
end
---- modulo; remainder of a divided by b %
+--- modulo; remainder of a divided by b **%**
-- @param a value
-- @param b value
function operator.mod(a,b)
return a%b
end
---- concatenate two values (either strings or __concat defined) ..
+--- concatenate two values (either strings or `__concat` defined) **..**
-- @param a value
-- @param b value
function operator.concat(a,b)
return a..b
end
---- return the negative of a value -
+--- return the negative of a value **-**
-- @param a value
--- @param b value
function operator.unm(a)
return -a
end
---- false if value evaluates as true not
+--- false if value evaluates as true **not**
-- @param a value
function operator.lnot(a)
return not a
end
---- true if both values evaluate as true and
+--- true if both values evaluate as true **and**
-- @param a value
-- @param b value
function operator.land(a,b)
return a and b
end
---- true if either value evaluate as true or
+--- true if either value evaluate as true **or**
-- @param a value
-- @param b value
function operator.lor(a,b)
return a or b
end
---- make a table from the arguments {}
+--- make a table from the arguments **{}**
-- @param ... non-nil arguments
-- @return a table
function operator.table (...)
return {...}
end
---- match two strings ~
+--- match two strings **~**.
-- uses @{string.find}
function operator.match (a,b)
return strfind(a,b)~=nil
@@ -174,6 +173,17 @@ function operator.nop (...)
return ...
end
+---- Map from operator symbol to function.
+-- Most of these map directly from operators;
+-- But note these extras
+--
+-- * __'()'__ `call`
+-- * __'[]'__ `index`
+-- * __'{}'__ `table`
+-- * __'~'__ `match`
+--
+-- @table optable
+-- @field operator
operator.optable = {
['+']=operator.add,
['-']=operator.sub,
diff --git a/files/lua/pl/path.lua b/files/lua/pl/path.lua
index ecf2b6d..772056f 100644
--- a/files/lua/pl/path.lua
+++ b/files/lua/pl/path.lua
@@ -33,13 +33,26 @@ end
attrib = attributes
path.attrib = attrib
path.link_attrib = link_attrib
+
+--- Lua iterator over the entries of a given directory.
+-- Behaves like `lfs.dir`
path.dir = lfs.dir
+
+--- Creates a directory.
path.mkdir = lfs.mkdir
+
+--- Removes a directory.
path.rmdir = lfs.rmdir
+
+---- Get the working directory.
+path.currentdir = currentdir
+
+--- Changes the working directory.
path.chdir = lfs.chdir
+
--- is this a directory?
--- @param P A file path
+-- @string P A file path
function path.isdir(P)
assert_string(1,P)
if P:match("\\$") then
@@ -49,14 +62,14 @@ function path.isdir(P)
end
--- is this a file?.
--- @param P A file path
+-- @string P A file path
function path.isfile(P)
assert_string(1,P)
return attrib(P,'mode') == 'file'
end
-- is this a symbolic link?
--- @param P A file path
+-- @string P A file path
function path.islink(P)
assert_string(1,P)
if link_attrib then
@@ -67,14 +80,14 @@ function path.islink(P)
end
--- return size of a file.
--- @param P A file path
+-- @string P A file path
function path.getsize(P)
assert_string(1,P)
return attrib(P,'size')
end
--- does a path exist?.
--- @param P A file path
+-- @string P A file path
-- @return the file path if it exists, nil otherwise
function path.exists(P)
assert_string(1,P)
@@ -82,20 +95,20 @@ function path.exists(P)
end
--- Return the time of last access as the number of seconds since the epoch.
--- @param P A file path
+-- @string P A file path
function path.getatime(P)
assert_string(1,P)
return attrib(P,'access')
end
--- Return the time of last modification
--- @param P A file path
+-- @string P A file path
function path.getmtime(P)
return attrib(P,'modification')
end
---Return the system's ctime.
--- @param P A file path
+-- @string P A file path
function path.getctime(P)
assert_string(1,P)
return path.attrib(P,'change')
@@ -133,7 +146,7 @@ local sep,dirsep = path.sep,path.dirsep
--- given a path, return the directory part and a file part.
-- if there's no directory part, the first value will be empty
--- @param P A file path
+-- @string P A file path
function path.splitpath(P)
assert_string(1,P)
local i = #P
@@ -150,8 +163,8 @@ function path.splitpath(P)
end
--- return an absolute path.
--- @param P A file path
--- @param pwd optional start path to use (default is current dir)
+-- @string P A file path
+-- @string[opt] pwd optional start path to use (default is current dir)
function path.abspath(P,pwd)
assert_string(1,P)
if pwd then assert_string(2,pwd) end
@@ -169,7 +182,9 @@ end
--- given a path, return the root part and the extension part.
-- if there's no extension part, the second value will be empty
--- @param P A file path
+-- @string P A file path
+-- @treturn string root part
+-- @treturn string extension part (maybe empty)
function path.splitext(P)
assert_string(1,P)
local i = #P
@@ -189,7 +204,7 @@ function path.splitext(P)
end
--- return the directory part of a path
--- @param P A file path
+-- @string P A file path
function path.dirname(P)
assert_string(1,P)
local p1,p2 = path.splitpath(P)
@@ -197,7 +212,7 @@ function path.dirname(P)
end
--- return the file part of a path
--- @param P A file path
+-- @string P A file path
function path.basename(P)
assert_string(1,P)
local p1,p2 = path.splitpath(P)
@@ -205,7 +220,7 @@ function path.basename(P)
end
--- get the extension part of a path.
--- @param P A file path
+-- @string P A file path
function path.extension(P)
assert_string(1,P)
local p1,p2 = path.splitext(P)
@@ -213,7 +228,7 @@ function path.extension(P)
end
--- is this an absolute path?.
--- @param P A file path
+-- @string P A file path
function path.isabs(P)
assert_string(1,P)
if path.is_windows then
@@ -224,10 +239,11 @@ function path.isabs(P)
end
--- return the path resulting from combining the individual paths.
--- if the second path is absolute, we return that path.
--- @param p1 A file path
--- @param p2 A file path
--- @param ... more file paths
+-- if the second (or later) path is absolute, we return the last absolute path (joined with any non-absolute paths following).
+-- empty elements (except the last) will be ignored.
+-- @string p1 A file path
+-- @string p2 A file path
+-- @string ... more file paths
function path.join(p1,p2,...)
assert_string(1,p1)
assert_string(2,p2)
@@ -242,7 +258,7 @@ function path.join(p1,p2,...)
end
if path.isabs(p2) then return p2 end
local endc = at(p1,#p1)
- if endc ~= path.sep and endc ~= other_sep then
+ if endc ~= path.sep and endc ~= other_sep and endc ~= "" then
p1 = p1..path.sep
end
return p1..p2
@@ -251,7 +267,7 @@ end
--- normalize the case of a pathname. On Unix, this returns the path unchanged;
-- for Windows, it converts the path to lowercase, and it also converts forward slashes
-- to backward slashes.
--- @param P A file path
+-- @string P A file path
function path.normcase(P)
assert_string(1,P)
if path.is_windows then
@@ -261,12 +277,12 @@ function path.normcase(P)
end
end
-local np_gen1,np_gen2 = '[^SEP]+SEP%.%.SEP?','SEP+%.?SEP'
+local np_gen1,np_gen2 = '([^SEP]+)SEP(%.%.SEP?)','SEP+%.?SEP'
local np_pat1, np_pat2
--- normalize a path name.
-- A//B, A/./B and A/foo/../B all become A/B.
--- @param P a file path
+-- @string P a file path
function path.normpath(P)
assert_string(1,P)
if path.is_windows then
@@ -284,8 +300,13 @@ function path.normpath(P)
P,k = P:gsub(np_pat2,sep)
until k == 0
repeat -- A/../ -> (empty)
- P,k = P:gsub(np_pat1,'')
- until k == 0
+ local oldP = P
+ P,k = P:gsub(np_pat1,function(D, up)
+ if D == '..' then return nil end
+ if D == '.' then return up end
+ return ''
+ end)
+ until k == 0 or oldP == P
if P == '' then P = '.' end
return P
end
@@ -298,8 +319,8 @@ local function ATS (P)
end
--- relative path from current directory or optional start point
--- @param P a path
--- @param start optional start point (default current directory)
+-- @string P a path
+-- @string[opt] start optional start point (default current directory)
function path.relpath (P,start)
assert_string(1,P)
if start then assert_string(2,start) end
@@ -328,7 +349,7 @@ end
--- Replace a starting '~' with the user's home directory.
-- In windows, if HOME isn't set, then USERPROFILE is used in preference to
-- HOMEDRIVE HOMEPATH. This is guaranteed to be writeable on all versions of Windows.
--- @param P A file path
+-- @string P A file path
function path.expanduser(P)
assert_string(1,P)
if at(P,1) == '~' then
@@ -344,16 +365,16 @@ end
---Return a suitable full path to a new temporary file name.
--- unlike os.tmpnam(), it always gives you a writeable path (uses %TMP% on Windows)
+-- unlike os.tmpnam(), it always gives you a writeable path (uses TEMP environment variable on Windows)
function path.tmpname ()
local res = tmpnam()
- if path.is_windows then res = getenv('TMP')..res end
+ if path.is_windows then res = getenv('TEMP')..res end
return res
end
--- return the largest common prefix path of two paths.
--- @param path1 a file path
--- @param path2 a file path
+-- @string path1 a file path
+-- @string path2 a file path
function path.common_prefix (path1,path2)
assert_string(1,path1)
assert_string(2,path2)
@@ -375,11 +396,10 @@ function path.common_prefix (path1,path2)
--return ''
end
-
--- return the full path where a particular Lua module would be found.
-- Both package.path and package.cpath is searched, so the result may
--- either be a Lua file or a shared libarary.
--- @param mod name of the module
+-- either be a Lua file or a shared library.
+-- @string mod name of the module
-- @return on success: path of module, lua or binary
-- @return on error: nil,error string
function path.package_path(mod)
diff --git a/files/lua/pl/platf/luajava.lua b/files/lua/pl/platf/luajava.lua
deleted file mode 100644
index 4fb82e6..0000000
--- a/files/lua/pl/platf/luajava.lua
+++ /dev/null
@@ -1,101 +0,0 @@
--- experimental support for LuaJava
---
-local path = {}
-
-
-path.link_attrib = nil
-
-local File = luajava.bindClass("java.io.File")
-local Array = luajava.bindClass('java.lang.reflect.Array')
-
-local function file(s)
- return luajava.new(File,s)
-end
-
-function path.dir(P)
- local ls = file(P):list()
- print(ls)
- local idx,n = -1,Array:getLength(ls)
- return function ()
- idx = idx + 1
- if idx == n then return nil
- else
- return Array:get(ls,idx)
- end
- end
-end
-
-function path.mkdir(P)
- return file(P):mkdir()
-end
-
-function path.rmdir(P)
- return file(P):delete()
-end
-
---- is this a directory?
--- @param P A file path
-function path.isdir(P)
- if P:match("\\$") then
- P = P:sub(1,-2)
- end
- return file(P):isDirectory()
-end
-
---- is this a file?.
--- @param P A file path
-function path.isfile(P)
- return file(P):isFile()
-end
-
--- is this a symbolic link?
--- Direct support for symbolic links is not provided.
--- see http://stackoverflow.com/questions/813710/java-1-6-determine-symbolic-links
--- and the caveats therein.
--- @param P A file path
-function path.islink(P)
- local f = file(P)
- local canon
- local parent = f:getParent()
- if not parent then
- canon = f
- else
- parent = f.getParentFile():getCanonicalFile()
- canon = luajava.new(File,parent,f:getName())
- end
- return canon:getCanonicalFile() ~= canon:getAbsoluteFile()
-end
-
---- return size of a file.
--- @param P A file path
-function path.getsize(P)
- return file(P):length()
-end
-
---- does a path exist?.
--- @param P A file path
--- @return the file path if it exists, nil otherwise
-function path.exists(P)
- return file(P):exists() and P
-end
-
---- Return the time of last access as the number of seconds since the epoch.
--- @param P A file path
-function path.getatime(P)
- return path.getmtime(P)
-end
-
---- Return the time of last modification
--- @param P A file path
-function path.getmtime(P)
- -- Java time is no. of millisec since the epoch
- return file(P):lastModified()/1000
-end
-
----Return the system's ctime.
--- @param P A file path
-function path.getctime(P)
- return path.getmtime(P)
-end
-
-return path
diff --git a/files/lua/pl/pretty.lua b/files/lua/pl/pretty.lua
index 8cc37d2..5319fed 100644
--- a/files/lua/pl/pretty.lua
+++ b/files/lua/pl/pretty.lua
@@ -9,8 +9,25 @@ local append = table.insert
local concat = table.concat
local utils = require 'pl.utils'
local lexer = require 'pl.lexer'
+local quote_string = require'pl.stringx'.quote_string
local assert_arg = utils.assert_arg
+--AAS
+--Perhaps this could be evolved into part of a "Compat5.3" library some day.
+--I didn't think that it was time for that, however.
+local tostring = tostring
+if _VERSION == "Lua 5.3" then
+ local _tostring = tostring
+ tostring = function(s)
+ if type(s) == "number" then
+ return ("%.f"):format(s)
+ else
+ return _tostring(s)
+ end
+ end
+
+end
+
local pretty = {}
local function save_string_index ()
@@ -35,15 +52,15 @@ end
-- An empty environment is used, and
-- any occurance of the keyword 'function' will be considered a problem.
-- in the given environment - the return value may be `nil`.
--- @param s {string} string of the form '{...}', with perhaps some whitespace
--- before or after the curly braces.
+-- @string s string of the form '{...}', with perhaps some whitespace
+-- before or after the curly braces.
-- @return a table
function pretty.read(s)
assert_arg(1,s,'string')
if s:find '^%s*%-%-' then -- may start with a comment..
s = s:gsub('%-%-.-\n','')
end
- if not s:find '^%s*%b{}%s*$' then return nil,"not a Lua table" end
+ if not s:find '^%s*{' then return nil,"not a Lua table" end
if s:find '[^\'"%w_]function[^\'"%w_]' then
local tok = lexer.lua(s)
for t,v in tok do
@@ -65,9 +82,9 @@ function pretty.read(s)
end
--- read a Lua chunk.
--- @param s Lua code
+-- @string s Lua code
-- @param env optional environment
--- @param paranoid prevent any looping constructs and disable string methods
+-- @bool paranoid prevent any looping constructs and disable string methods
-- @return the environment
function pretty.load (s, env, paranoid)
env = env or {}
@@ -93,7 +110,8 @@ end
local function quote_if_necessary (v)
if not v then return ''
else
- if v:find ' ' then v = '"'..v..'"' end
+ --AAS
+ if v:find ' ' then v = quote_string(v) end
end
return v
end
@@ -108,12 +126,17 @@ local function quote (s)
if type(s) == 'table' then
return pretty.write(s,'')
else
- return ('%q'):format(tostring(s))
+ --AAS
+ return quote_string(s)-- ('%q'):format(tostring(s))
end
end
local function index (numkey,key)
- if not numkey then key = quote(key) end
+ --AAS
+ if not numkey then
+ key = quote(key)
+ key = key:find("^%[") and (" " .. key .. " ") or key
+ end
return '['..key..']'
end
@@ -123,17 +146,17 @@ end
-- extra value. Normally puts out one item per line, using
-- the provided indent; set the second parameter to '' if
-- you want output on one line.
--- @param tbl {table} Table to serialize to a string.
--- @param space {string} (optional) The indent to use.
--- Defaults to two spaces; make it the empty string for no indentation
--- @param not_clever {bool} (optional) Use for plain output, e.g {['key']=1}.
--- Defaults to false.
+-- @tab tbl Table to serialize to a string.
+-- @string space (optional) The indent to use.
+-- Defaults to two spaces; make it the empty string for no indentation
+-- @bool not_clever (optional) Use for plain output, e.g {['key']=1}.
+-- Defaults to false.
-- @return a string
-- @return a possible error message
function pretty.write (tbl,space,not_clever)
if type(tbl) ~= 'table' then
local res = tostring(tbl)
- if type(tbl) == 'string' then res = '"'..res..'"' end
+ if type(tbl) == 'string' then return quote(tbl) end
return res, 'not a table'
end
if not keywords then
@@ -178,11 +201,13 @@ function pretty.write (tbl,space,not_clever)
if tp ~= 'string' and tp ~= 'table' then
putln(quote_if_necessary(tostring(t))..',')
elseif tp == 'string' then
- if t:find('\n') then
- putln('[[\n'..t..']],')
- else
- putln(quote(t)..',')
- end
+ -- if t:find('\n') then
+ -- putln('[[\n'..t..']],')
+ -- else
+ -- putln(quote(t)..',')
+ -- end
+ --AAS
+ putln(quote_string(t) ..",")
elseif tp == 'table' then
if tables[t] then
putln('value()
method for
--- retrieving the string value.
+-- The resulting object has an extra `value()` method for
+-- retrieving the string value. Implements `file:write`, `file:seek`, `file:lines`,
+-- plus an extra `writef` method which works like `utils.printf`.
-- @usage f = create(); f:write('hello, dolly\n'); print(f:value())
function stringio.create()
return setmetatable({tbl={}},SW)
end
--- create a file-like object for reading from a given string.
--- @param s The input string.
+-- Implements `file:read`.
+-- @string s The input string.
+-- @usage fs = open '20 10'; x,y = f:read ('*n','*n'); assert(x == 20 and y == 10)
function stringio.open(s)
return setmetatable({str=s,i=1},SR)
end
diff --git a/files/lua/pl/stringx.lua b/files/lua/pl/stringx.lua
index 0e66c2a..9bae615 100644
--- a/files/lua/pl/stringx.lua
+++ b/files/lua/pl/stringx.lua
@@ -11,7 +11,7 @@
local utils = require 'pl.utils'
local string = string
local find = string.find
-local type,setmetatable,getmetatable,ipairs,unpack = type,setmetatable,getmetatable,ipairs,unpack
+local type,setmetatable,getmetatable,ipairs,unpack = type,setmetatable,getmetatable,ipairs,utils.unpack
local error,tostring = error,tostring
local gsub = string.gsub
local rep = string.rep
@@ -37,60 +37,55 @@ end
local stringx = {}
+------------------
+-- String Predicates
+-- @section predicates
+
--- does s only contain alphabetic characters?.
--- @param s a string
+-- @string s a string
function stringx.isalpha(s)
assert_string(1,s)
return find(s,'^%a+$') == 1
end
--- does s only contain digits?.
--- @param s a string
+-- @string s a string
function stringx.isdigit(s)
assert_string(1,s)
return find(s,'^%d+$') == 1
end
--- does s only contain alphanumeric characters?.
--- @param s a string
+-- @string s a string
function stringx.isalnum(s)
assert_string(1,s)
return find(s,'^%w+$') == 1
end
--- does s only contain spaces?.
--- @param s a string
+-- @string s a string
function stringx.isspace(s)
assert_string(1,s)
return find(s,'^%s+$') == 1
end
--- does s only contain lower case characters?.
--- @param s a string
+-- @string s a string
function stringx.islower(s)
assert_string(1,s)
return find(s,'^[%l%s]+$') == 1
end
--- does s only contain upper case characters?.
--- @param s a string
+-- @string s a string
function stringx.isupper(s)
assert_string(1,s)
return find(s,'^[%u%s]+$') == 1
end
---- concatenate the strings using this string as a delimiter.
--- @param self the string
--- @param seq a table of strings or numbers
--- @usage (' '):join {1,2,3} == '1 2 3'
-function stringx.join (self,seq)
- assert_string(1,self)
- return concat(seq,self)
-end
-
--- does string start with the substring?.
--- @param self the string
--- @param s2 a string
+-- @string self the string
+-- @string s2 a string
function stringx.startswith(self,s2)
assert_string(1,self)
assert_string(2,s2)
@@ -103,16 +98,16 @@ local function _find_all(s,sub,first,last)
local res
local k = 0
while i1 do
+ if last and i1 > last then break end
res = i1
k = k + 1
i1,i2 = find(s,sub,i2+1,true)
- if last and i1 > last then break end
end
return res,k
end
--- does string end with the given substring?.
--- @param s a string
+-- @string s a string
-- @param send a substring or a table of suffixes
function stringx.endswith(s,send)
assert_string(1,s)
@@ -129,8 +124,20 @@ function stringx.endswith(s,send)
end
end
--- break string into a list of lines
--- @param self the string
+--- Strings and Lists
+-- @section lists
+
+--- concatenate the strings using this string as a delimiter.
+-- @string self the string
+-- @param seq a table of strings or numbers
+-- @usage (' '):join {1,2,3} == '1 2 3'
+function stringx.join (self,seq)
+ assert_string(1,self)
+ return concat(seq,self)
+end
+
+--- break string into a list of lines
+-- @string self the string
-- @param keepends (currently not used)
function stringx.splitlines (self,keepends)
assert_string(1,self)
@@ -140,72 +147,11 @@ function stringx.splitlines (self,keepends)
return setmetatable(res,list_MT)
end
-local function tab_expand (self,n)
- return (gsub(self,'([^\t]*)\t', function(s)
- return s..(' '):rep(n - #s % n)
- end))
-end
-
---- replace all tabs in s with n spaces. If not specified, n defaults to 8.
--- with 0.9.5 this now correctly expands to the next tab stop (if you really
--- want to just replace tabs, use :gsub('\t',' ') etc)
--- @param self the string
--- @param n number of spaces to expand each tab, (default 8)
-function stringx.expandtabs(self,n)
- assert_string(1,self)
- n = n or 8
- if not self:find '\n' then return tab_expand(self,n) end
- local res,i = {},1
- for line in stringx.lines(self) do
- res[i] = tab_expand(line,n)
- i = i + 1
- end
- return table.concat(res,'\n')
-end
-
---- find index of first instance of sub in s from the left.
--- @param self the string
--- @param sub substring
--- @param i1 start index
-function stringx.lfind(self,sub,i1)
- assert_string(1,self)
- assert_string(2,sub)
- local idx = find(self,sub,i1,true)
- if idx then return idx else return nil end
-end
-
---- find index of first instance of sub in s from the right.
--- @param self the string
--- @param sub substring
--- @param first first index
--- @param last last index
-function stringx.rfind(self,sub,first,last)
- assert_string(1,self)
- assert_string(2,sub)
- local idx = _find_all(self,sub,first,last)
- if idx then return idx else return nil end
-end
-
---- replace up to n instances of old by new in the string s.
--- if n is not present, replace all instances.
--- @param s the string
--- @param old the target substring
--- @param new the substitution
--- @param n optional maximum number of substitutions
--- @return result string
--- @return the number of substitutions
-function stringx.replace(s,old,new,n)
- assert_string(1,s)
- assert_string(1,old)
- return (gsub(s,escape(old),new:gsub('%%','%%%%'),n))
-end
-
--- split a string into a list of strings using a delimiter.
--- @class function
--- @name split
--- @param self the string
--- @param re a delimiter (defaults to whitespace)
--- @param n maximum number of results
+-- @function split
+-- @string self the string
+-- @string[opt] re a delimiter (defaults to whitespace)
+-- @int n maximum number of results
-- @usage #(('one two'):split()) == 2
-- @usage ('one,two,three'):split(',') == List{'one','two','three'}
-- @usage ('one,two,three'):split(',',2) == List{'one','two,three'}
@@ -223,14 +169,67 @@ function stringx.split(self,re,n)
return setmetatable(res,list_MT)
end
---- split a string using a pattern. Note that at least one value will be returned!
--- @param self the string
--- @param re a Lua string pattern (defaults to whitespace)
--- @return the parts of the string
--- @usage a,b = line:splitv('=')
-function stringx.splitv (self,re)
+local function tab_expand (self,n)
+ return (gsub(self,'([^\t]*)\t', function(s)
+ return s..(' '):rep(n - #s % n)
+ end))
+end
+
+--- replace all tabs in s with n spaces. If not specified, n defaults to 8.
+-- with 0.9.5 this now correctly expands to the next tab stop (if you really
+-- want to just replace tabs, use :gsub('\t',' ') etc)
+-- @string self the string
+-- @int n number of spaces to expand each tab, (default 8)
+function stringx.expandtabs(self,n)
assert_string(1,self)
- return utils.splitv(self,re)
+ n = n or 8
+ if not self:find '\n' then return tab_expand(self,n) end
+ local res,i = {},1
+ for line in stringx.lines(self) do
+ res[i] = tab_expand(line,n)
+ i = i + 1
+ end
+ return table.concat(res,'\n')
+end
+
+--- Finding and Replacing
+-- @section find
+
+--- find index of first instance of sub in s from the left.
+-- @string self the string
+-- @string sub substring
+-- @int i1 start index
+function stringx.lfind(self,sub,i1)
+ assert_string(1,self)
+ assert_string(2,sub)
+ local idx = find(self,sub,i1,true)
+ if idx then return idx else return nil end
+end
+
+--- find index of first instance of sub in s from the right.
+-- @string self the string
+-- @string sub substring
+-- @int first first index
+-- @int last last index
+function stringx.rfind(self,sub,first,last)
+ assert_string(1,self)
+ assert_string(2,sub)
+ local idx = _find_all(self,sub,first,last)
+ if idx then return idx else return nil end
+end
+
+--- replace up to n instances of old by new in the string s.
+-- if n is not present, replace all instances.
+-- @string s the string
+-- @string old the target substring
+-- @string new the substitution
+-- @int[opt] n optional maximum number of substitutions
+-- @return result string
+-- @return the number of substitutions
+function stringx.replace(s,old,new,n)
+ assert_string(1,s)
+ assert_string(1,old)
+ return (gsub(s,escape(old),new:gsub('%%','%%%%'),n))
end
local function copy(self)
@@ -238,14 +237,17 @@ local function copy(self)
end
--- count all instances of substring in string.
--- @param self the string
--- @param sub substring
+-- @string self the string
+-- @string sub substring
function stringx.count(self,sub)
assert_string(1,self)
local i,k = _find_all(self,sub,1)
return k
end
+--- Stripping and Justifying
+-- @section strip
+
local function _just(s,w,ch,left,right)
local n = #s
if w > n then
@@ -270,9 +272,9 @@ local function _just(s,w,ch,left,right)
end
--- left-justify s with width w.
--- @param self the string
--- @param w width of justification
--- @param ch padding character, default ' '
+-- @string self the string
+-- @int w width of justification
+-- @string[opt=''] ch padding character
function stringx.ljust(self,w,ch)
assert_string(1,self)
assert_arg(2,w,'number')
@@ -280,9 +282,9 @@ function stringx.ljust(self,w,ch)
end
--- right-justify s with width w.
--- @param s the string
--- @param w width of justification
--- @param ch padding character, default ' '
+-- @string s the string
+-- @int w width of justification
+-- @string[opt=''] ch padding character
function stringx.rjust(s,w,ch)
assert_string(1,s)
assert_arg(2,w,'number')
@@ -290,9 +292,9 @@ function stringx.rjust(s,w,ch)
end
--- center-justify s with width w.
--- @param s the string
--- @param w width of justification
--- @param ch padding character, default ' '
+-- @string s the string
+-- @int w width of justification
+-- @string[opt=''] ch padding character
function stringx.center(s,w,ch)
assert_string(1,s)
assert_arg(2,w,'number')
@@ -321,8 +323,9 @@ local function _strip(s,left,right,chrs)
end
--- trim any whitespace on the left of s.
--- @param self the string
--- @param chrs default space, can be a string of characters to be trimmed
+-- @string self the string
+-- @string[opt='%x'] chrs default any whitespace character,
+-- but can be a string of characters to be trimmed
function stringx.lstrip(self,chrs)
assert_string(1,self)
return _strip(self,true,false,chrs)
@@ -330,21 +333,36 @@ end
lstrip = stringx.lstrip
--- trim any whitespace on the right of s.
--- @param s the string
--- @param chrs default space, can be a string of characters to be trimmed
+-- @string s the string
+-- @string[opt='%x'] chrs default any whitespace character,
+-- but can be a string of characters to be trimmed
function stringx.rstrip(s,chrs)
assert_string(1,s)
return _strip(s,false,true,chrs)
end
--- trim any whitespace on both left and right of s.
--- @param self the string
--- @param chrs default space, can be a string of characters to be trimmed
+-- @string self the string
+-- @string[opt='%x'] chrs default any whitespace character,
+-- but can be a string of characters to be trimmed
function stringx.strip(self,chrs)
assert_string(1,self)
return _strip(self,true,true,chrs)
end
+--- Partioning Strings
+-- @section partioning
+
+--- split a string using a pattern. Note that at least one value will be returned!
+-- @string self the string
+-- @string[opt='%s'] re a Lua string pattern (defaults to whitespace)
+-- @return the parts of the string
+-- @usage a,b = line:splitv('=')
+function stringx.splitv (self,re)
+ assert_string(1,self)
+ return utils.splitv(self,re)
+end
+
-- The partition functions split a string using a delimiter into three parts:
-- the part before, the delimiter itself, and the part afterwards
local function _partition(p,delim,fn)
@@ -358,8 +376,8 @@ local function _partition(p,delim,fn)
end
--- partition the string using first occurance of a delimiter
--- @param self the string
--- @param ch delimiter
+-- @string self the string
+-- @string ch delimiter
-- @return part before ch
-- @return ch
-- @return part after ch
@@ -370,8 +388,8 @@ function stringx.partition(self,ch)
end
--- partition the string p using last occurance of a delimiter
--- @param self the string
--- @param ch delimiter
+-- @string self the string
+-- @string ch delimiter
-- @return part before ch
-- @return ch
-- @return part after ch
@@ -382,8 +400,8 @@ function stringx.rpartition(self,ch)
end
--- return the 'character' at the index.
--- @param self the string
--- @param idx an index (can be negative)
+-- @string self the string
+-- @int idx an index (can be negative)
-- @return a substring of length 1 if successful, empty string otherwise.
function stringx.at(self,idx)
assert_string(1,self)
@@ -391,8 +409,11 @@ function stringx.at(self,idx)
return sub(self,idx,idx)
end
+--- Miscelaneous
+-- @section misc
+
--- return an interator over all lines in a string
--- @param self the string
+-- @string self the string
-- @return an iterator
function stringx.lines (self)
assert_string(1,self)
@@ -403,7 +424,7 @@ end
--- iniital word letters uppercase ('title case').
-- Here 'words' mean chunks of non-space characters.
--- @param self the string
+-- @string self the string
-- @return a string with each word's first letter uppercase
function stringx.title(self)
return (self:gsub('(%S)(%S*)',function(f,r)
@@ -417,9 +438,9 @@ local elipsis = '...'
local n_elipsis = #elipsis
--- return a shorted version of a string.
--- @param self the string
--- @param sz the maxinum size allowed
--- @param tail true if we want to show the end of the string (head otherwise)
+-- @string self the string
+-- @int sz the maxinum size allowed
+-- @bool tail true if we want to show the end of the string (head otherwise)
function stringx.shorten(self,sz,tail)
if #self > sz then
if sz < n_elipsis then return elipsis:sub(1,sz) end
@@ -433,6 +454,54 @@ function stringx.shorten(self,sz,tail)
return self
end
+--- Utility function that finds any patterns that match a long string's an open or close.
+-- Note that having this function use the least number of equal signs that is possible is a harder algorithm to come up with.
+-- Right now, it simply returns the greatest number of them found.
+-- @param s The string
+-- @return 'nil' if not found. If found, the maximum number of equal signs found within all matches.
+local function has_lquote(s)
+ local lstring_pat = '([%[%]])(=*)%1'
+ local start, finish, bracket, equals, next_equals = nil, 0, nil, nil, nil
+ -- print("checking lquote for", s)
+ repeat
+ start, finish, bracket, next_equals = s:find(lstring_pat, finish + 1)
+ if start then
+ -- print("found start", start, finish, bracket, next_equals)
+ --length of captured =. Ex: [==[ is 2, ]] is 0.
+ next_equals = #next_equals
+ equals = next_equals >= (equals or 0) and next_equals or equals
+ end
+ until not start
+ --next_equals will be nil if there was no match.
+ return equals
+end
+
+--- Quote the given string and preserve any control or escape characters, such that reloading the string in Lua returns the same result.
+-- @param s The string to be quoted.
+-- @return The quoted string.
+function stringx.quote_string(s)
+ --find out if there are any embedded long-quote
+ --sequences that may cause issues.
+ --This is important when strings are embedded within strings, like when serializing.
+ local equal_signs = has_lquote(s)
+ if s:find("\n") or equal_signs then
+ -- print("going with long string:", s)
+ equal_signs = ("="):rep((equal_signs or -1) + 1)
+ --long strings strip out leading \n. We want to retain that, when quoting.
+ if s:find("^\n") then s = "\n" .. s end
+ --if there is an embedded sequence that matches a long quote, then
+ --find the one with the maximum number of = signs and add one to that number
+ local lbracket, rbracket =
+ "[" .. equal_signs .. "[",
+ "]" .. equal_signs .. "]"
+ s = lbracket .. s .. rbracket
+ else
+ --Escape funny stuff.
+ s = ("%q"):format(s)
+ end
+ return s
+end
+
function stringx.import(dont_overload)
utils.import(stringx,string)
end
diff --git a/files/lua/pl/tablex.lua b/files/lua/pl/tablex.lua
index c83df67..e8fbe01 100644
--- a/files/lua/pl/tablex.lua
+++ b/files/lua/pl/tablex.lua
@@ -2,13 +2,14 @@
--
-- See @{02-arrays.md.Useful_Operations_on_Tables|the Guide}
--
--- Dependencies: `pl.utils`
+-- Dependencies: `pl.utils`, `pl.types`
-- @module pl.tablex
local utils = require ('pl.utils')
+local types = require ('pl.types')
local getmetatable,setmetatable,require = getmetatable,setmetatable,require
-local append,remove = table.insert,table.remove
+local tsort,append,remove = table.sort,table.insert,table.remove
local min,max = math.min,math.max
-local pairs,type,unpack,next,select,tostring = pairs,type,unpack,next,select,tostring
+local pairs,type,unpack,next,select,tostring = pairs,type,utils.unpack,next,select,tostring
local function_arg = utils.function_arg
local Set = utils.stdmt.Set
local List = utils.stdmt.List
@@ -29,43 +30,32 @@ local function makelist (res)
return setmetatable(res,List)
end
-local function check_meta (val)
- if type(val) == 'table' then return true end
- return getmetatable(val)
-end
-
local function complain (idx,msg)
error(('argument %d is not %s'):format(idx,msg),3)
end
local function assert_arg_indexable (idx,val)
- local mt = check_meta(val)
- if mt == true then return end
- if not(mt and mt.__len and mt.__index) then
+ if not types.is_indexable(val) then
complain(idx,"indexable")
end
end
local function assert_arg_iterable (idx,val)
- local mt = check_meta(val)
- if mt == true then return end
- if not(mt and mt.__pairs) then
+ if not types.is_iterable(val) then
complain(idx,"iterable")
end
end
local function assert_arg_writeable (idx,val)
- local mt = check_meta(val)
- if mt == true then return end
- if not(mt and mt.__newindex) then
+ if not types.is_writeable(val) then
complain(idx,"writeable")
end
end
-
--- copy a table into another, in-place.
--- @param t1 destination table
--- @param t2 source (any iterable object)
+-- @within Copying
+-- @tab t1 destination table
+-- @tab t2 source (actually any iterable object)
-- @return first table
function tablex.update (t1,t2)
assert_arg_writeable(1,t1)
@@ -82,7 +72,7 @@ end
-- be greater or equal. The difference gives the size of
-- the hash part, for practical purposes. Works for any
-- object with a __pairs metamethod.
--- @param t a table
+-- @tab t a table
-- @return the size
function tablex.size (t)
assert_arg_iterable(1,t)
@@ -92,7 +82,8 @@ function tablex.size (t)
end
--- make a shallow copy of a table
--- @param t an iterable source
+-- @within Copying
+-- @tab t an iterable source
-- @return new table
function tablex.copy (t)
assert_arg_iterable(1,t)
@@ -105,7 +96,8 @@ end
--- make a deep copy of a table, recursively copying all the keys and fields.
-- This will also set the copied table's metatable to that of the original.
--- @param t A table
+-- @within Copying
+-- @tab t A table
-- @return new table
function tablex.deepcopy(t)
if type(t) ~= 'table' then return t end
@@ -126,10 +118,11 @@ local abs, deepcompare = math.abs
--- compare two values.
-- if they are tables, then compare their keys and fields recursively.
+-- @within Comparing
-- @param t1 A value
-- @param t2 A value
--- @param ignore_mt if true, ignore __eq metamethod (default false)
--- @param eps if defined, then used for any number comparisons
+-- @bool[opt] ignore_mt if true, ignore __eq metamethod (default false)
+-- @number[opt] eps if defined, then used for any number comparisons
-- @return true or false
function tablex.deepcompare(t1,t2,ignore_mt,eps)
local ty1 = type(t1)
@@ -143,23 +136,27 @@ function tablex.deepcompare(t1,t2,ignore_mt,eps)
-- as well as tables which have the metamethod __eq
local mt = getmetatable(t1)
if not ignore_mt and mt and mt.__eq then return t1 == t2 end
+ for k1 in pairs(t1) do
+ if t2[k1]==nil then return false end
+ end
+ for k2 in pairs(t2) do
+ if t1[k2]==nil then return false end
+ end
for k1,v1 in pairs(t1) do
local v2 = t2[k1]
- if v2 == nil or not deepcompare(v1,v2,ignore_mt,eps) then return false end
- end
- for k2,v2 in pairs(t2) do
- local v1 = t1[k2]
- if v1 == nil or not deepcompare(v1,v2,ignore_mt,eps) then return false end
+ if not deepcompare(v1,v2,ignore_mt,eps) then return false end
end
+
return true
end
deepcompare = tablex.deepcompare
--- compare two arrays using a predicate.
--- @param t1 an array
--- @param t2 an array
--- @param cmp A comparison function
+-- @within Comparing
+-- @array t1 an array
+-- @array t2 an array
+-- @func cmp A comparison function
function tablex.compare (t1,t2,cmp)
assert_arg_indexable(1,t1)
assert_arg_indexable(2,t2)
@@ -172,8 +169,9 @@ function tablex.compare (t1,t2,cmp)
end
--- compare two list-like tables using an optional predicate, without regard for element order.
--- @param t1 a list-like table
--- @param t2 a list-like table
+-- @within Comparing
+-- @array t1 a list-like table
+-- @array t2 a list-like table
-- @param cmp A comparison function (may be nil)
function tablex.compare_no_order (t1,t2,cmp)
assert_arg_indexable(1,t1)
@@ -202,9 +200,10 @@ end
--- return the index of a value in a list.
-- Like string.find, there is an optional index to start searching,
-- which can be negative.
--- @param t A list-like table (i.e. with numerical indices)
+-- @within Finding
+-- @array t A list-like table
-- @param val A value
--- @param idx index to start; -1 means last element,etc (default 1)
+-- @int idx index to start; -1 means last element,etc (default 1)
-- @return index of value or nil if not found
-- @usage find({10,20,30},20) == 2
-- @usage find({'a','b','a','c'},'a',2) == 3
@@ -221,7 +220,8 @@ end
--- return the index of a value in a list, searching from the end.
-- Like string.find, there is an optional index to start searching,
-- which can be negative.
--- @param t A list-like table (i.e. with numerical indices)
+-- @within Finding
+-- @array t A list-like table
-- @param val A value
-- @param idx index to start; -1 means last element,etc (default 1)
-- @return index of value or nil if not found
@@ -238,8 +238,9 @@ end
--- return the index (or key) of a value in a table using a comparison function.
--- @param t A table
--- @param cmp A comparison function
+-- @within Finding
+-- @tab t A table
+-- @func cmp A comparison function
-- @param arg an optional second argument to the function
-- @return index of value, or nil if not found
-- @return value returned by comparison function
@@ -254,8 +255,8 @@ function tablex.find_if(t,cmp,arg)
end
--- return a list of all values in a table indexed by another list.
--- @param tbl a table
--- @param idx an index table (a list of keys)
+-- @tab tbl a table
+-- @array idx an index table (a list of keys)
-- @return a list-like table
-- @usage index_by({10,20,30,40},{2,4}) == {20,40}
-- @usage index_by({one=1,two=2,three=3},{'one','three'}) == {1,3}
@@ -272,8 +273,9 @@ end
--- apply a function to all values of a table.
-- This returns a table of the results.
-- Any extra arguments are passed to the function.
--- @param fun A function that takes at least one argument
--- @param t A table
+-- @within MappingAndFiltering
+-- @func fun A function that takes at least one argument
+-- @tab t A table
-- @param ... optional arguments
-- @usage map(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900,fred=4}
function tablex.map(fun,t,...)
@@ -289,8 +291,9 @@ end
--- apply a function to all values of a list.
-- This returns a table of the results.
-- Any extra arguments are passed to the function.
--- @param fun A function that takes at least one argument
--- @param t a table (applies to array part)
+-- @within MappingAndFiltering
+-- @func fun A function that takes at least one argument
+-- @array t a table (applies to array part)
-- @param ... optional arguments
-- @return a list-like table
-- @usage imap(function(v) return v*v end, {10,20,30,fred=2}) is {100,400,900}
@@ -305,8 +308,9 @@ function tablex.imap(fun,t,...)
end
--- apply a named method to values from a table.
--- @param name the method name
--- @param t a list-like table
+-- @within MappingAndFiltering
+-- @string name the method name
+-- @array t a list-like table
-- @param ... any extra arguments to the method
function tablex.map_named_method (name,t,...)
utils.assert_string(1,name)
@@ -320,41 +324,44 @@ function tablex.map_named_method (name,t,...)
return setmeta(res,t,List)
end
-
--- apply a function to all values of a table, in-place.
-- Any extra arguments are passed to the function.
--- @param fun A function that takes at least one argument
--- @param t a table
+-- @func fun A function that takes at least one argument
+-- @tab t a table
-- @param ... extra arguments
function tablex.transform (fun,t,...)
assert_arg_iterable(1,t)
fun = function_arg(1,fun)
for k,v in pairs(t) do
- t[v] = fun(v,...)
+ t[k] = fun(v,...)
end
end
---- generate a table of all numbers in a range
--- @param start number
--- @param finish number
--- @param step optional increment (default 1 for increasing, -1 for decreasing)
+--- generate a table of all numbers in a range.
+-- This is consistent with a numerical for loop.
+-- @int start number
+-- @int finish number
+-- @int[opt=1] step make this negative for start < finish
function tablex.range (start,finish,step)
- if start == finish then return {start}
- elseif start > finish then return {}
+ local res
+ step = step or 1
+ if start == finish then
+ res = {start}
+ elseif (start > finish and step > 0) or (finish > start and step < 0) then
+ res = {}
+ else
+ local k = 1
+ res = {}
+ for i=start,finish,step do res[k]=i; k=k+1 end
end
- local res = {}
- local k = 1
- if not step then
- if finish > start then step = finish > start and 1 or -1 end
- end
- for i=start,finish,step do res[k]=i; k=k+1 end
- return res
+ return makelist(res)
end
--- apply a function to values from two tables.
--- @param fun a function of at least two arguments
--- @param t1 a table
--- @param t2 a table
+-- @within MappingAndFiltering
+-- @func fun a function of at least two arguments
+-- @tab t1 a table
+-- @tab t2 a table
-- @param ... extra arguments
-- @return a table
-- @usage map2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23,m=44}
@@ -371,9 +378,10 @@ end
--- apply a function to values from two arrays.
-- The result will be the length of the shortest array.
--- @param fun a function of at least two arguments
--- @param t1 a list-like table
--- @param t2 a list-like table
+-- @within MappingAndFiltering
+-- @func fun a function of at least two arguments
+-- @array t1 a list-like table
+-- @array t2 a list-like table
-- @param ... extra arguments
-- @usage imap2('+',{1,2,3,m=4},{10,20,30,m=40}) is {11,22,23}
function tablex.imap2 (fun,t1,t2,...)
@@ -388,8 +396,8 @@ function tablex.imap2 (fun,t1,t2,...)
end
--- 'reduce' a list using a binary function.
--- @param fun a function of two arguments
--- @param t a list-like table
+-- @func fun a function of two arguments
+-- @array t a list-like table
-- @return the result of the function
-- @usage reduce('+',{1,2,3,4}) == 10
function tablex.reduce (fun,t)
@@ -405,10 +413,11 @@ end
--- apply a function to all elements of a table.
-- The arguments to the function will be the value,
--- the key and finally any extra arguments passed to this function.
--- Note that the Lua 5.0 function table.foreach passed the key first.
--- @param t a table
--- @param fun a function with at least one argument
+-- the key and _finally_ any extra arguments passed to this function.
+-- Note that the Lua 5.0 function table.foreach passed the _key_ first.
+-- @within Iterating
+-- @tab t a table
+-- @func fun a function with at least one argument
-- @param ... extra arguments
function tablex.foreach(t,fun,...)
assert_arg_iterable(1,t)
@@ -420,9 +429,10 @@ end
--- apply a function to all elements of a list-like table in order.
-- The arguments to the function will be the value,
--- the index and finally any extra arguments passed to this function
--- @param t a table
--- @param fun a function with at least one argument
+-- the index and _finally_ any extra arguments passed to this function
+-- @within Iterating
+-- @array t a table
+-- @func fun a function with at least one argument
-- @param ... optional arguments
function tablex.foreachi(t,fun,...)
assert_arg_indexable(1,t)
@@ -432,13 +442,13 @@ function tablex.foreachi(t,fun,...)
end
end
-
--- Apply a function to a number of tables.
-- A more general version of map
-- The result is a table containing the result of applying that function to the
-- ith value of each table. Length of output list is the minimum length of all the lists
--- @param fun a function of n arguments
--- @param ... n tables
+-- @within MappingAndFiltering
+-- @func fun a function of n arguments
+-- @tab ... n tables
-- @usage mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}) is {111,222,333}
-- @usage mapn(math.max, {1,20,300},{10,2,3},{100,200,100}) is {100,200,300}
-- @param fun A function that takes as many arguments as there are tables
@@ -465,8 +475,9 @@ end
-- The function can return a value and a key (note the order!). If both
-- are not nil, then this pair is inserted into the result. If only value is not nil, then
-- it is appended to the result.
--- @param fun A function which will be passed each key and value as arguments, plus any extra arguments to pairmap.
--- @param t A table
+-- @within MappingAndFiltering
+-- @func fun A function which will be passed each key and value as arguments, plus any extra arguments to pairmap.
+-- @tab t A table
-- @param ... optional arguments
-- @usage pairmap(function(k,v) return v end,{fred=10,bonzo=20}) is {10,20} _or_ {20,10}
-- @usage pairmap(function(k,v) return {k,v},k end,{one=1,two=2}) is {one={'one',1},two={'two',2}}
@@ -488,7 +499,8 @@ end
local function keys_op(i,v) return i end
--- return all the keys of a table in arbitrary order.
--- @param t A table
+-- @within Extraction
+-- @tab t A table
function tablex.keys(t)
assert_arg_iterable(1,t)
return makelist(tablex.pairmap(keys_op,t))
@@ -497,7 +509,8 @@ end
local function values_op(i,v) return v end
--- return all the values of the table in arbitrary order
--- @param t A table
+-- @within Extraction
+-- @tab t A table
function tablex.values(t)
assert_arg_iterable(1,t)
return makelist(tablex.pairmap(values_op,t))
@@ -507,7 +520,7 @@ local function index_map_op (i,v) return i,v end
--- create an index map from a list-like table. The original values become keys,
-- and the associated values are the indices into the original list.
--- @param t a list-like table
+-- @array t a list-like table
-- @return a map-like table
function tablex.index_map (t)
assert_arg_indexable(1,t)
@@ -518,20 +531,20 @@ local function set_op(i,v) return true,v end
--- create a set from a list-like table. A set is a table where the original values
-- become keys, and the associated values are all true.
--- @param t a list-like table
+-- @array t a list-like table
-- @return a set (a map-like table)
function tablex.makeset (t)
assert_arg_indexable(1,t)
return setmetatable(tablex.pairmap(set_op,t),Set)
end
-
--- combine two tables, either as union or intersection. Corresponds to
-- set operations for sets () but more general. Not particularly
-- useful for list-like tables.
--- @param t1 a table
--- @param t2 a table
--- @param dup true for a union, false for an intersection.
+-- @within Merging
+-- @tab t1 a table
+-- @tab t2 a table
+-- @bool dup true for a union, false for an intersection.
-- @usage merge({alice=23,fred=34},{bob=25,fred=34}) is {fred=34}
-- @usage merge({alice=23,fred=34},{bob=25,fred=34},true) is {bob=25,fred=34,alice=23}
-- @see tablex.index_map
@@ -553,28 +566,29 @@ end
--- a new table which is the difference of two tables.
-- With sets (where the values are all true) this is set difference and
-- symmetric difference depending on the third parameter.
--- @param s1 a map-like table or set
--- @param s2 a map-like table or set
--- @param symm symmetric difference (default false)
+-- @within Merging
+-- @tab s1 a map-like table or set
+-- @tab s2 a map-like table or set
+-- @bool symm symmetric difference (default false)
-- @return a map-like table or set
function tablex.difference (s1,s2,symm)
assert_arg_iterable(1,s1)
assert_arg_iterable(2,s2)
local res = {}
for k,v in pairs(s1) do
- if not s2[k] then res[k] = v end
+ if s2[k] == nil then res[k] = v end
end
if symm then
for k,v in pairs(s2) do
- if not s1[k] then res[k] = v end
+ if s1[k] == nil then res[k] = v end
end
end
return setmeta(res,s1,Map)
end
--- A table where the key/values are the values and value counts of the table.
--- @param t a list-like table
--- @param cmp a function that defines equality (otherwise uses ==)
+-- @array t a list-like table
+-- @func cmp a function that defines equality (otherwise uses ==)
-- @return a map-like table
-- @see seq.count_map
function tablex.count_map (t,cmp)
@@ -590,7 +604,13 @@ function tablex.count_map (t,cmp)
res[v] = 1 -- there's at least one instance
for j = i+1,n do
local w = t[j]
- if cmp and cmp(v,w) or v == w then
+ local ok
+ if cmp then
+ ok = cmp(v,w)
+ else
+ ok = v == w
+ end
+ if ok then
res[v] = res[v] + 1
mask[w] = true
end
@@ -600,9 +620,10 @@ function tablex.count_map (t,cmp)
return setmetatable(res,Map)
end
---- filter a table's values using a predicate function
--- @param t a list-like table
--- @param pred a boolean function
+--- filter an array's values using a predicate function
+-- @within MappingAndFiltering
+-- @array t a list-like table
+-- @func pred a boolean function
-- @param arg optional argument to be passed as second argument of the predicate
function tablex.filter (t,pred,arg)
assert_arg_indexable(1,t)
@@ -620,7 +641,9 @@ end
--- return a table where each element is a table of the ith values of an arbitrary
-- number of tables. It is equivalent to a matrix transpose.
+-- @within Merging
-- @usage zip({10,20,30},{100,200,300}) is {{10,100},{20,200},{30,300}}
+-- @array ... arrays to be zipped
function tablex.zip(...)
return tablex.mapn(function(...) return {...} end,...)
end
@@ -652,24 +675,26 @@ function _copy (dest,src,idest,isrc,nsrc,clean_tail)
return dest
end
---- copy an array into another one, resizing the destination if necessary. --- @param dest a list-like table --- @param src a list-like table --- @param idest where to start copying values from source (default 1) --- @param isrc where to start copying values into destination (default 1) --- @param nsrc number of elements to copy from source (default source size) +--- copy an array into another one, clearing `dest` after `idest+nsrc`, if necessary. +-- @within Copying +-- @array dest a list-like table +-- @array src a list-like table +-- @int[opt=1] idest where to start copying values into destination +-- @int[opt=1] isrc where to start copying values from source +-- @int[opt=#src] nsrc number of elements to copy from source function tablex.icopy (dest,src,idest,isrc,nsrc) assert_arg_indexable(1,dest) assert_arg_indexable(2,src) return _copy(dest,src,idest,isrc,nsrc,true) end ---- copy an array into another one.
--- @param dest a list-like table --- @param src a list-like table --- @param idest where to start copying values from source (default 1) --- @param isrc where to start copying values into destination (default 1) --- @param nsrc number of elements to copy from source (default source size) +--- copy an array into another one. +-- @within Copying +-- @array dest a list-like table +-- @array src a list-like table +-- @int[opt=1] idest where to start copying values into destination +-- @int[opt=1] isrc where to start copying values from source +-- @int[opt=#src] nsrc number of elements to copy from source function tablex.move (dest,src,idest,isrc,nsrc) assert_arg_indexable(1,dest) assert_arg_indexable(2,src) @@ -690,9 +715,10 @@ end -- If first or last are negative then they are relative to the end of the list -- eg. sub(t,-2) gives last 2 entries in a list, and -- sub(t,-4,-2) gives from -4th to -2nd --- @param t a list-like table --- @param first An index --- @param last An index +-- @within Extraction +-- @array t a list-like table +-- @int first An index +-- @int last An index -- @return a new List function tablex.sub(t,first,last) assert_arg_indexable(1,t) @@ -704,14 +730,14 @@ end --- set an array range to a value. If it's a function we use the result -- of applying it to the indices. --- @param t a list-like table +-- @array t a list-like table -- @param val a value --- @param i1 start range (default 1) --- @param i2 end range (default table size) +-- @int[opt=1] i1 start range +-- @int[opt=#t] i2 end range function tablex.set (t,val,i1,i2) assert_arg_indexable(1,t) i1,i2 = i1 or 1,i2 or #t - if utils.is_callable(val) then + if types.is_callable(val) then for i = i1,i2 do t[i] = val(i) end @@ -723,8 +749,8 @@ function tablex.set (t,val,i1,i2) end --- create a new array of specified size with initial value. --- @param n size --- @param val initial value (can be nil, but don't expect # to work!) +-- @int n size +-- @param val initial value (can be `nil`, but don't expect `#` to work!) -- @return the table function tablex.new (n,val) local res = {} @@ -733,17 +759,20 @@ function tablex.new (n,val) end --- clear out the contents of a table. --- @param t a table +-- @array t a list -- @param istart optional start position function tablex.clear(t,istart) istart = istart or 1 for i = istart,#t do remove(t) end end ---- insert values into a table.
--- insertvalues(t, [pos,] values)
--- similar to table.insert but inserts values from given table "values", --- not the object itself, into table "t" at position "pos". +--- insert values into a table. +-- similar to `table.insert` but inserts values from given table `values`, +-- not the object itself, into table `t` at position `pos`. +-- @within Copying +-- @array t the list +-- @int[opt] position (default is at end) +-- @array values function tablex.insertvalues(t, ...) assert_arg(1,t,'table') local pos, values @@ -765,9 +794,10 @@ function tablex.insertvalues(t, ...) end --- remove a range of values from a table. --- @param t a list-like table --- @param i1 start index --- @param i2 end index +-- End of range may be negative. +-- @array t a list-like table +-- @int i1 start index +-- @int i2 end index -- @return the table function tablex.removevalues (t,i1,i2) assert_arg(1,t,'table') @@ -800,9 +830,10 @@ _find = function (t,value,tables) end --- find a value in a table by recursive search. --- @param t the table +-- @within Finding +-- @tab t the table -- @param value the value --- @param exclude any tables to avoid searching +-- @array[opt] exclude any tables to avoid searching -- @usage search(_G,math.sin,{package.path}) == 'math.sin' -- @return a fieldspec, e.g. 'a.b' or 'math.sin' function tablex.search (t,value,exclude) @@ -814,4 +845,54 @@ function tablex.search (t,value,exclude) return _find(t,value,tables) end +--- return an iterator to a table sorted by its keys +-- @within Iterating +-- @tab t the table +-- @func f an optional comparison function (f(x,y) is true if x < y) +-- @usage for k,v in tablex.sort(t) do print(k,v) end +-- @return an iterator to traverse elements sorted by the keys +function tablex.sort(t,f) + local keys = {} + for k in pairs(t) do keys[#keys + 1] = k end + tsort(keys,f) + local i = 0 + return function() + i = i + 1 + return keys[i], t[keys[i]] + end +end + +--- return an iterator to a table sorted by its values +-- @within Iterating +-- @tab t the table +-- @func f an optional comparison function (f(x,y) is true if x < y) +-- @usage for k,v in tablex.sortv(t) do print(k,v) end +-- @return an iterator to traverse elements sorted by the values +function tablex.sortv(t,f) + local rev = {} + for k,v in pairs(t) do rev[v] = k end + local next = tablex.sort(rev,f) + return function() + local value,key = next() + return key,value + end +end + +--- modifies a table to be read only. +-- This only offers weak protection. Tables can still be modified with +-- `table.insert` and `rawset`. +-- @tab t the table +-- @return the table read only. +function tablex.readonly(t) + local mt = { + __index=t, + __newindex=function(t, k, v) error("Attempt to modify read-only table", 2) end, + __pairs=function() return pairs(t) end, + __ipairs=function() return ipairs(t) end, + __len=function() return #t end, + __metatable=false + } + return setmetatable({}, mt) +end + return tablex diff --git a/files/lua/pl/template.lua b/files/lua/pl/template.lua index 5c992ae..05480e8 100644 --- a/files/lua/pl/template.lua +++ b/files/lua/pl/template.lua @@ -29,31 +29,32 @@ -- @module pl.template local utils = require 'pl.utils' -local append,format = table.insert,string.format + local function parseHashLines(chunk,brackets,esc) + local append,format,strsub,strfind = table.insert,string.format,string.sub,string.find local exec_pat = "()$(%b"..brackets..")()" local function parseDollarParen(pieces, chunk, s, e) local s = 1 for term, executed, e in chunk:gmatch (exec_pat) do - executed = '('..executed:sub(2,-2)..')' + executed = '('..strsub(executed,2,-2)..')' append(pieces, - format("%q..(%s or '')..",chunk:sub(s, term - 1), executed)) + format("%q..(%s or '')..",strsub(chunk,s, term - 1), executed)) s = e end - append(pieces, format("%q", chunk:sub(s))) + append(pieces, format("%q", strsub(chunk,s))) end local esc_pat = esc.."+([^\n]*\n?)" local esc_pat1, esc_pat2 = "^"..esc_pat, "\n"..esc_pat local pieces, s = {"return function(_put) ", n = 1}, 1 while true do - local ss, e, lua = chunk:find (esc_pat1, s) + local ss, e, lua = strfind (chunk,esc_pat1, s) if not e then - ss, e, lua = chunk:find(esc_pat2, s) + ss, e, lua = strfind(chunk,esc_pat2, s) append(pieces, "_put(") - parseDollarParen(pieces, chunk:sub(s, ss)) + parseDollarParen(pieces, strsub(chunk,s, ss)) append(pieces, ")") if not e then break end end @@ -67,13 +68,14 @@ end local template = {} --- expand the template using the specified environment. --- @param str the template string --- @param env the environment (by default empty).
--- There are three special fields in the environment table
-
---
_parent
continue looking up in this table
--- _brackets
; default is '()', can be any suitable bracket pair
--- _escape
; default is '#'
---