Name the functions found at a particular line number in some JavaScript source
JavaScript parser, mangler/compressor and beautifier toolkit
A light, featureful and explicit option parsing library.
Lightweight Node.js version sniffing/comparison
A JSON Object of css color names mapped to their hex value
A Gherkin linter/validator written in javascript
CLI arguments parser. Native port of python's argparse.
A small polyfill for Object.setprototypeof
A source map library purpose-build for the Parcel bundler with a focus on fast combining and manipulating of source-maps.
ECMAScript AST recursive visitor
Give me a string and I'll tell you if it's a valid npm package name
Parse an SMTP (RFC-5321) address
A collection of SCSS-specific rules for Stylelint
[](https://travis-ci.org/stefanpenner/get-caller-file) [](https://ci.a
Trace the original position through a source map
Resolve the path of a module like `require.resolve()` but from a given path
type-check allows you to check the types of JavaScript values at runtime with a Haskell like type syntax.
Sanitize strings for use as JavaScript identifiers & property names
yet another unzip library for node
A list of directories you should ignore by default
Light ECMAScript (JavaScript) Value Notation - human written, concise, typed, flexible
Convert a kebab-cased CSS property into a camelCased DOM property.
Compare, format, diff and serialize any JavaScript value
HTTP methods that node supports
This CocoaPods plugin linking functionality allows to easily test their pods. This plugin adds the following commands to the CococPods command line: * pod link <name> * pod unlink <name> * pod list links
This CocoaPods plugin linking functionality allows to easily test their pods. This plugin adds the following commands to the CocoaPods command line: * pod link <name> * pod unlink <name> * pod list links
Welcome to the "Knuckleheads" game, a (slightly enhanced version of the) fully functional text driven game written in Ruby 1.9 and used as a teaching aid for the excellent web-based Ruby course administered by Pragmatic Studios. W00t! W00t! Before playing, fill the default csv file "players.csv" with any number of players by putting the name of each followed by their initial health (an integer separated from the name by a comma). Each player must be on a new line. You may also create an alternate file formatted the same way with a different name. (A sample alternate player file called "more_nuts.csv" is provided as an example.) Invoke an alternate file simply by entering the file name when prompted. How fun is that!! To start the game, start the Ruby file "studio_game.rb" in Ruby 1.9 (or later should work as well) and follow the directions. By the way, there are two players added in hard code with modified behavior. One is "sandro" a LOSER or "clumsy" player who damages his treasure upon collecting it, resulting in the value of each treasure being worth only half of it's original value. What a klutz! The other is a wild-ass player, "bright_eyes." He's a serious partyier! After every other player is spent, he "finds the last beer in the fridge" and can't help but w00t. Bright_eyes starts off just like everyone else. But after being w00ted six times he goes freak'n w00t-crazy and w00ts EVERY TIME! You better think long and hard before committing to joining Mr. bright_eyes. Legend has it that he eats the livers of his prey after he parties them to death, and in this way remains an immortal player! Now fire up that command line for some outrageous fun! CAUTION: It is addictive. My cousin, Schneebo (the Schneebo on my Dad's side, NOT Schneebo Salifronski) once sat down and played Knuckleheads non-stop for 176.39 hours. It's the truth - you've been warned. Oh, and by the way: Three w00ts for Pragmatic Studios - woot, woot, h00ray! - woot, woot, h00ray! - woot, woot, h00ray!
Ame Ame provides a simple command-line interface API for Ruby¹. It can be used to provide both simple interfaces like that of ‹rm›² and complex ones like that of ‹git›³. It uses Ruby’s own classes, methods, and argument lists to provide an interface that is both simple to use from the command-line side and from the Ruby side. The provided command-line interface is flexible and follows commond standards for command-line processing. ¹ See http://ruby-lang.org/ ² See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/rm.html ³ See http://git-scm.com/docs/ § Usage Let’s begin by looking at two examples, one where we mimic the POSIX¹ command-line interface to the ‹rm› command. Looking at the entry² in the standard, ‹rm› takes the following options: = -f. = Do not prompt for confirmation. = -i. = Prompt for confirmation. = -R. = Remove file hierarchies. = -r. = Equivalent to /-r/. It also takes the following arguments: = FILE. = A pathname or directory entry to be removed. And actually allows one or more of these /FILE/ arguments to be given. We also note that the ‹rm› command is described as a command to “remove directory entries”. ¹ See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html ² See http://pubs.opengroup.org/onlinepubs/9699919799/utilities/rm.html Let’s turn this specification into one using Ame’s API. We begin by adding a flag for each of the options listed above: class Rm < Ame::Root flag 'f', '', false, 'Do not prompt for confirmation' flag 'i', '', nil, 'Prompt for confirmation' do |options| options['f'] = false end flag 'R', '', false, 'Remove file hierarchies' flag 'r', '', nil, 'Equivalent to -R' do |options| options['r'] = true end A flag¹ is a boolean option that doesn’t take an argument. Each flag gets a short and long name, where an empty name means that there’s no corresponding short or long name for the flag, a default value (true, false, or nil), and a description of what the flag does. Each flag can also optionally take a block that can do further processing. In this case we use this block to modify the Hash that maps option names to their values passed to the block to set other flags’ values than the ones that the block is associated with. As these flags (‘i’ and ‘r’) aren’t themselves of interest, their default values have been set to nil, which means that they won’t be included in the Hash that maps option names to their values when passed to the method. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#flag-class-method There are quite a few other kinds of options besides flags that can be defined using Ame, but flags are all that are required for this example. We’ll get to the other kinds in later examples. Next we add a “splus” argument. splus 'FILE', String, 'File to remove' A splus¹ argument is like a Ruby “splat”, that is, an Array argument at the end of the argument list to a method preceded by a star, except that a splus requires at least one argument. A splus argument gets a name for the argument (‹FILE›), the type of argument it represents (String), and a description. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#splus-class-method Then we add a description of the command (method) itself: description 'Remove directory entries' Descriptions¹ will be used in help output to assist the user in using the command. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#description-class-method Finally, we add the Ruby method that’ll implement the command (all preceding code included here for completeness): class Rm < Ame::Root version '1.0.0' flag 'f', '', false, 'Do not prompt for confirmation' flag 'i', '', nil, 'Prompt for confirmation' do |options| options['f'] = false end flag 'R', '', false, 'Remove file hierarchies' flag 'r', '', nil, 'Equivalent to -R' do |options| options['r'] = true end splus 'FILE', String, 'File to remove' description 'Remove directory entries' def rm(files, options = {}) require 'fileutils' FileUtils.send options['R'] ? :rm_r : :rm, [first] + rest, :force => options['f'] end end Actually, another bit of code was also added, namely version '1.0.0' This sets the version¹ String of the command. This information is used when the command is invoked with the “‹--version›” flag. This flag is automatically added, so you don’t need to add it yourself. Another flag, “‹--help›”, is also added automatically. When given, this flag’ll make Ame output usage information of the command. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#version-class-method To actually run the command, all you need to do is invoke Rm.process This’ll invoke the command using the command-line arguments stored in ‹ARGV›, but you can also specify other ones if you want to: Rm.process 'rm', %w[-r /tmp/*] The first argument to #process¹ is the name of the method to invoke, which defaults to ‹File.basename($0)›, and the second argument is an Array of Strings that should be processed as command-line arguments passed to the command. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#process-class-method If you’d store the complete ‹Rm› class defined above in a file called ‹rm› and add ‹#! /usr/bin/ruby -w› at the beginning and ‹Rm.process› at the end, you’d have a fully functional ‹rm› command (after making it executable). Let’s see it in action: % rm --help Usage: rm [OPTIONS]... FILE... Remove directory entries Arguments: FILE... File to remove Options: -R Remove file hierarchies -f Do not prompt for confirmation --help Display help for this method -i Prompt for confirmation -r Equivalent to -R --version Display version information % rm --version rm 1.0.0 Some commands are more complex than ‹rm›. For example, ‹git›¹ has a rather complex command-line interface. We won’t mimic it all here, but let’s introduce the rest of the Ame API using a fake ‹git› clone as an example. ¹ See http://git-scm.com/docs/ ‹Git› uses sub-commands to achieve most things. Implementing sub-commands with Ame is done using a “dispatch”. We’ll discuss dispatches in more detail later, but suffice it to say that a dispatch delegates processing to a child class that’ll handle the sub-command in question. We begin by defining our main ‹git› command using a class called ‹Git› under the ‹Git::CLI› namespace: module Git end class Git::CLI < Ame::Root version '1.0.0' class Git < Ame::Class description 'The stupid content tracker' def initialize; end We’re setting things up to use the ‹Git› class as a dispatch in the ‹Git::CLI› class. The description on the ‹initialize› method will be used as a description of the ‹git› dispatch command itself. Next, let’s add the ‹format-patch›¹ sub-command: description 'Prepare patches for e-mail submission' flag ?n, 'numbered', false, 'Name output in [PATCH n/m] format' flag ?N, 'no-numbered', nil, 'Name output in [PATCH] format' do |options| options['numbered'] = false end toggle ?s, 'signoff', false, 'Add Signed-off-by: line to the commit message' switch '', 'thread', 'STYLE', nil, Ame::Types::Enumeration[:shallow, :deep], 'Controls addition of In-Reply-To and References headers' flag '', 'no-thread', nil, 'Disables addition of In-Reply-To and Reference headers' do |options, _| options.delete 'thread' end option '', 'start-number', 'N', 1, 'Start numbering the patches at N instead of 1' multioption '', 'to', 'ADDRESS', String, 'Add a To: header to the email headers' optional 'SINCE', 'N/A', 'Generate patches for commits after SINCE' def format_patch(since = '', options = {}) p since, options end ¹ See http://git-scm.com/docs/git-format-patch/ We’re using quite a few new Ame commands here. Let’s look at each in turn: toggle ?s, 'signoff', false, 'Add Signed-off-by: line to the commit message' A “toggle”¹ is a flag that also has an inverse. Beyond the flags ‘s’ and “signoff”, the toggle also defines “no-signoff”, which will set “signoff” to false. This is useful if you want to support configuration files that set “signoff”’s default to true, but still allow it to be overridden on the command line. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#toggle-class-method When using the short form of a toggle (and flag and switch), multiple ones may be juxtaposed after the initial one. For example, “‹-sn›” is equivalent to “‹-s -n›” to “git format-patch›”. switch '', 'thread', 'STYLE', nil, Ame::Types::Enumeration[:shallow, :deep], 'Controls addition of In-Reply-To and References headers' A “switch”¹ is an option that takes an optional argument. This allows you to have separate defaults for when the switch isn’t present on the command line and for when it’s given without an argument. The third argument to a switch is the name of the argument. We’re also introducing a new concept here in ‹Ame::Types::Enumeration›. An enumeration² allows you to limit the allowed input to a set of Symbols. An enumeration also has a default value in the first item to its constructor (which is aliased as ‹.[]›). In this case, the “thread” switch defaults to nil, but, when given, will default to ‹:shallow› if no argument is given. If an argument is given it must be either “shallow” or “deep”. A switch isn’t required to take an enumeration as its argument default and can take any kind of default value for its argument that Ame knows how to handle. We’ll look at this in more detail later, but know that the type of the default value will be used to inform Ame how to parse a command-line argument into a Ruby value. An argument to a switch must be given, in this case, as “‹--thread=deep›” on the command line. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#switch-class-method ² See http://disu.se/software/ame-1.0/api/user/Ame/Types/Enumeration/ option '', 'start-number', 'N', 1, 'Start numbering the patches at N instead of 1' An “option”¹ is an option that takes an argument. The argument must always be present and may be given, in this case, as “‹--start-number=2›” or “‹--start-number 2›” on the command line. For a short-form option, anything that follows the option is seen as an argument, so assuming that “start-number” also had a short name of ‘S’, “‹-S2›” would be equivalent to “‹-S 2›”, which would be equivalent to “‹--start-number 2›”. Note that “‹-snS2›” would still work as expected. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#option-class-method multioption '', 'to', 'ADDRESS', String, 'Add a To: header to the email headers' A “multioption”¹ is an option that takes an argument and may be repeated any number of times. Each argument will be added to an Array stored in the Hash that maps option names to their values. Instead of taking a default argument, it takes a type for the argument (String, in this case). Again, types are used to inform Ame how to parse command-line arguments into Ruby values. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#multioption-class-method optional 'SINCE', 'N/A', 'Generate patches for commits after SINCE' An “optional”¹ argument is an argument that isn’t required. If it’s not present on the command line it’ll get its default value (the String ‹'N/A'›, in this case). ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#optional-class-method We’ve now covered all kinds of options and one new kind of argument. There are three more types of argument (one that we’ve already seen and two new) that we’ll look into now: “argument”, “splat”, and “splus”. description 'Annotate file lines with commit information' argument 'FILE', String, 'File to annotate' def annotate(file) p file end An “argument”¹ is an argument that’s required. If it’s not present on the command line, an error will be raised (and by default reported to the terminal). As it’s required, it doesn’t take a default, but rather a type. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#argument-class-method description 'Add file contents to the index' splat 'PATHSPEC', String, 'Files to add content from' def add(paths) p paths end A “splat”¹ is an argument that’s not required, but may be given any number of times. The type of a splat is the type of one argument and the type of a splat as a whole is an Array of values of that type. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#splat-class-method description 'Display gitattributes information' splus 'PATHNAME', String, 'Files to list attributes of' def check_attr(paths) p paths end A “splus”¹ is an argument that’s required, but may also be given any number of times. The type of a splus is the type of one argument and the type of a splus as a whole is an Array of values of that type. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#splus-class-method Now that we’ve seen all kinds of options and arguments, let’s look on an additional tool at our disposal, the dispatch¹. class Remote < Ame::Class description 'Manage set of remote repositories' def initialize; end description 'Shows a list of existing remotes' flag 'v', 'verbose', false, 'Show remote URL after name' def list(options = {}) p options end description 'Adds a remote named NAME for the repository at URL' argument 'name', String, 'Name of the remote to add' argument 'url', String, 'URL to the repository of the remote to add' def add(name, url) p name, url end end ¹ See http://disu.se/software/ame-1.0/api/user/Ame/Class#dispatch-class-method Here we’re defining a child class to Git::CLI::Git called “Remote” that doesn’t introduce anything new. Then we set up the dispatch: dispatch Remote, :default => 'list' This adds a method called “remote” to Git::CLI::Git that will dispatch processing of the command line to an instance of the Remote class when “‹git remote›” is seen on the command line. The “remote” method expects an argument that’ll be used to decide what sub-command to execute. Here we’ve specified that in the absence of such an argument, the “list” method should be invoked. We add the same kind of dispatch to Git under Git::CLI: dispatch Git and then we’re done. Here’s all the previous code in its entirety: module Git end class Git::CLI < Ame::Root version '1.0.0' class Git < Ame::Class description 'The stupid content tracker' def initialize; end description 'Prepare patches for e-mail submission' flag ?n, 'numbered', false, 'Name output in [PATCH n/m] format' flag ?N, 'no-numbered', nil, 'Name output in [PATCH] format' do |options| options['numbered'] = false end toggle ?s, 'signoff', false, 'Add Signed-off-by: line to the commit message' switch '', 'thread', 'STYLE', nil, Ame::Types::Enumeration[:shallow, :deep], 'Controls addition of In-Reply-To and References headers' flag '', 'no-thread', nil, 'Disables addition of In-Reply-To and Reference headers' do |options, _| options.delete 'thread' end option '', 'start-number', 'N', 1, 'Start numbering the patches at N instead of 1' multioption '', 'to', 'ADDRESS', String, 'Add a To: header to the email headers' optional 'SINCE', 'N/A', 'Generate patches for commits after SINCE' def format_patch(since = '', options = {}) p since, options end description 'Annotate file lines with commit information' argument 'FILE', String, 'File to annotate' def annotate(file) p file end description 'Add file contents to the index' splat 'PATHSPEC', String, 'Files to add content from' def add(paths) p paths end description 'Display gitattributes information' splus 'PATHNAME', String, 'Files to list attributes of' def check_attr(paths) p paths end class Remote < Ame::Class description 'Manage set of remote repositories' def initialize; end description 'Shows a list of existing remotes' flag 'v', 'verbose', false, 'Show remote URL after name' def list(options = {}) p options end description 'Adds a remote named NAME for the repository at URL' argument 'name', String, 'Name of the remote to add' argument 'url', String, 'URL to the repository of the remote to add' def add(name, url) p name, url end end dispatch Remote, :default => 'list' end dispatch Git end If we put this code in a file called “git” and add ‹#! /usr/bin/ruby -w› at the beginning and ‹Git::CLI.process› at the end, you’ll have a very incomplete git command-line interface on your hands. Let’s look at what some of its ‹--help› output looks like: % git --help Usage: git [OPTIONS]... METHOD [ARGUMENTS]... The stupid content tracker Arguments: METHOD Method to run [ARGUMENTS]... Arguments to pass to METHOD Options: --help Display help for this method --version Display version information Methods: add Add file contents to the index annotate Annotate file lines with commit information check-attr Display gitattributes information format-patch Prepare patches for e-mail submission remote Manage set of remote repositories % git format-patch --help Usage: git format-patch [OPTIONS]... [SINCE] Prepare patches for e-mail submission Arguments: [SINCE=N/A] Generate patches for commits after SINCE Options: -N, --no-numbered Name output in [PATCH] format --help Display help for this method -n, --numbered Name output in [PATCH n/m] format --no-thread Disables addition of In-Reply-To and Reference headers -s, --signoff Add Signed-off-by: line to the commit message --start-number=N Start numbering the patches at N instead of 1 --thread[=STYLE] Controls addition of In-Reply-To and References headers --to=ADDRESS* Add a To: header to the email headers % git remote --help Usage: git remote [OPTIONS]... [METHOD] [ARGUMENTS]... Manage set of remote repositories Arguments: [METHOD=list] Method to run [ARGUMENTS]... Arguments to pass to METHOD Options: --help Display help for this method Methods: add Adds a remote named NAME for the repository at URL list Shows a list of existing remotes § API The previous section gave an introduction to the whole user API in an informal and introductory way. For an indepth reference to the user API, see the {user API documentation}¹. ¹ See http://disu.se/software/ame-1.0/api/user/Ame/ If you want to extend the API or use it in some way other than as a command-line-interface writer, see the {developer API documentation}¹. ¹ See http://disu.se/software/ame-1.0/api/developer/Ame/ § Financing Currently, most of my time is spent at my day job and in my rather busy private life. Please motivate me to spend time on this piece of software by donating some of your money to this project. Yeah, I realize that requesting money to develop software is a bit, well, capitalistic of me. But please realize that I live in a capitalistic society and I need money to have other people give me the things that I need to continue living under the rules of said society. So, if you feel that this piece of software has helped you out enough to warrant a reward, please PayPal a donation to now@disu.se¹. Thanks! Your support won’t go unnoticed! ¹ Send a donation: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=now@disu.se&item_name=Ame § Reporting Bugs Please report any bugs that you encounter to the {issue tracker}¹. ¹ See https://github.com/now/ame/issues § Authors Nikolai Weibull wrote the code, the tests, the documentation, and this README. § Licensing Ame is free software: you may redistribute it and/or modify it under the terms of the {GNU Lesser General Public License, version 3}¹ or later², as published by the {Free Software Foundation}³. ¹ See http://disu.se/licenses/lgpl-3.0/ ² See http://gnu.org/licenses/ ³ See http://fsf.org/
== PintosCheck -- Auto Pintos Checker to Save the Day == == Functionalities == The functionality of this simple script is to download pintos homework assignments from the mail inbox and then run through all the desired tests and finally generate reports in plain text or html formats, all automatically. == Requirements For Running PintosCheck == Since all the scripts are written in ruby, PintosCheck require ruby installed on the system. I use ruby 1.8.7 for development, but ruby 1.9.* versions are expected to function as well. However, ruby 1.8.6 and lower versions are not supported. For information of downloading and installing ruby, see http://www.ruby-lang.org/en/downloads/. In addition to ruby itself, RubyGems 1.3.* is also required because it hosts the installation source for this project and almost all other ruby projects as well. To download or update RubyGems, please go to http://gemcutter.org/pages/download for more information. == Installation == Once you have all the requirements on your system, it's really easy to install PintosCheck. In the UNIX shell or Windows command line environment, type the following command(sudo if needed): gem install pintoscheck --include-dependencies Go grab a cup of coffee, and PintosCheck will automatically download and install itself onto the system. To check the installation, type 'ptschk --version', and if something like 'PintosCheck 0.1.0' pops up then you're green to go! == Finally, how do I check my students' pintos homework? == This project ships with a 'ptschk' command tool. This tool needs a task configuration file to actually do everything. The configuration file is in YAML format, which is basically a recursive key-value pair representation. If you're using PintosCheck for the first time, there's a very nice command line option to generate the skeleton for you. Just run 'ptschk init my_first_task.config' and a file named 'my_first_task.config' will be generated for you. Inside this file there is a set of the minimal options for the task to run properly, and you just have to fill in what you need. After you set up your configuration file, run 'ptschk run my_first_task.config' and the tasks will kick off immediately, and after a while the report will be generated. A detailed configuration options for advanced task setup will be available in production release of this project.
# SecureDataBag / Knife Secure Bag Knife Secure Bag provides a consistent interface to DataBagItem, EncryptedDataBagItem as well as the custom created SecureDataBagItem while also providing a few extra handy features to help in your DataBag workflows. SecureDataBagItem, can not only manage your existing DataBagItems and EncryptedDataBagItems, but it also provides you with a DataBag type which enables you to selectively encrypt only some of the fields in your DataBag thus allowing you to be able to search for the remaining fields. ## Installation To build and install the plugin add it your Gemfile or run: ```shell gem install secure_data_bag ``` ## Configuration #### Knife Secure Bag Defaults for the Knife command may be provided in your _knife.rb_ file. ```ruby knife[:secure_data_bag][:encrypted_keys] = %w( password ssh_keys ssh_ids public_keys private_keys keys secret ) knife[:secure_data_bag][:secret_file] = "#{local_dir}/secret.pem" knife[:secure_data_bag][:export_root] = "#{kitchen_dir}/data_bags" knife[:secure_data_bag][:export_on_upload] = true knife[:secure_data_bag][:defaults][:secrets][:export_format] = 'plain' ``` To break this up: `knife[:secure_data_bag][:encrypted_keys] = []` When Knife Secure Bag encrypts a hash with an _encryption format_ of *nested*, it will recursively walk through the hash from the bottom up and encrypt any key found within this array. `knife[:secure_data_bag][:secret_file]` When encryption is required, the shared secret found at this location will be loaded. `knife[:secure_data_bag][:export_root]` When exporting a data\_bag\_item, files will be created in below this root directory. Typically this would be the data\_bag folder located within your kitchen. `knife[:secure_data_bag][:export_on_upload]` When a data\_bag\_item is edited using `knife secure bag edit`, it may be automatically exported to the _export\_root_. `knife[:secure_data_bag][:defaults][:secrets][:export_format]` The configuration file additionally supports the _defaults_ hash which provides default values for all _command line arguments_ that one might use. Of all of them only the _export\_format_ key is likely to be of much use. ## Examples #### Chef cookbook recipe ```ruby metadata = {} # Define the keys we wish to encrypt metadata[:encrypted_keys] = %w(encoded) # Optionally load a specific shared secret. Otherwise, the global # encrypted\_data\_bag\_secret will be automatically used. secret_key = SecureDataBagItem.load_key("/path/to/secret") # Create a hash of data to use as an exampe raw_data = { id: "item", data_bag: "data_bag", encoded: "my string", unencoded: "other string" } # Instantiate a SecureDataBagItem from a hash item = SecureDataBagItem.from_hash(data, metadata) # Or more explicitely item = SecureDataBagItem.from_hash(data, encrypted_keys: %w(encoded)) # Or load from server item = SecureDataBagItem.load("data_bag", "item") # Print the un-encrypted raw data pp item.raw_data # Print the un-encrypted `encoded` key pp item['encoded'] # Print the encrypted hash as a data_bag_item hash pp item.to_hash =begin { id: "item", data_bag: "data_bag", encoded: { encrypted_data: "encoded", cipher: aes-256-cbc, iv: 13453453dkgfefg== version: 1 } unencoded: "other string", } =end ``` ## Usage #### Knife commands Print an DataBagItem, EncryptedDataBagItem or SecureDataBagItem, auto-detecting the encryption method used as plain text. ```shell knife secure bag show -F js secrets secret_item ``` Print an DataBagItem, EncryptedDataBagItem or SecureDataBagItem, auto-detecting the encryption method used as a SecureDataBagItem in encrypted format. ```shell knife secure bag show -F js secrets secret_item --enc-format nested ``` Edit an EncryptedDataBagItem, preserve it's encryption type, and export a copy to the _data\_bag_ folder in your kitchen. ```shell knife secure bag edit secrets secret_item --export ``` ## Knife SubCommands Most of the SubCommands support the following command-line options: `--enc-format [plain,encrypted,nested]` Ensure that, when displaying or uploading the data\_bag\_item, we forcibly encrypt the data\_bag\_item using the specified format instead of preserving the existing format. In this case: - plain: refers to a DataBagItem - encrypted: refers to an EnrytpedDataBagItem - nested: refers to a SecureDataBagItem `--dec-format [plain,encrypted,nested]` Attempt to decrypt the data\_bag\_item using the given format rather than the auto-detected one. The only real reason to use this is when you wish to specifically select _plain_ as the format so as to not decrypt the item. `--enc-keys key1,key2,key3` Provide a comma delimited list of hash keys which should be encrypted when encrypting the data\_bag\_item. This list will be concatenated with any key names listed in the configuration file or which were previously encrypted. `--export` Export the data\_bag\_item to json file in either of _export-format_ or _enc-format_. `--export-format` Overrides the encryption format only for the _export_ feature. `--export-root` Root directly under which a folder should exist for each _data_bag_ into which to export _data_bag_items_ as json files. When displaying the content of the _data\_bag\_item_, an additional key of *_secure_metadata* will be added to the output which contains gem specific metadata such as the encryption formats and any encrypted keys found. This key will _not_ be saved with the item, however it may be manipulated to alter the behavior of the _edit_ or _export_ commands. #### knife secure bag show DATA_BAG ITEM This command functions just like `knife data bag show` and is used to print out the content of either a DataBagItem, EncryptedDataBagItem or SecureDataBagItem. By default, it will auto-detect the Item type, and print it's unencrypted version to the terminal. This behavior, however, may be altered using the previously mentioned command line options. #### knife secure bag open PATH This commands functions much like `knife secure bag show`, however it is designed to load a _data\_bag\_item_ from disk as opposed to loading it from Chef server. This may be of use when view the content of an exported encrypted file. #### knife secure bag edit DATA_BAG DATA_BAG_ITEM This command functions just like `knife data bag edit` and is used to edit either a DataBagItem, EncryptedDataBagItem or a SecureDataBagItem. It supports all of the same options as `knife secure bag show`. #### knife secure bag from file DATA_BAG PATH This command functions just like `knife data bag from file` and is used to upload either a DataBagItem, EncryptedDataBagItem or a SecureDataBagItem. It supports all of the same options as `knife secure bag show`. ## Recipe DSL The gem additionally provides a few Recipe DSL methods which may be useful. ```ruby load_secure_item = secure_data_bag_item( data_bag_name, data_bag_item, cache: false ) load_plain_item = data_bag_item(data_bag_name, data_bag_item) convert_plain_to_secure = secure_data_bag_item!(load_plain_item) ```
Value Value is a library for defining immutable value objects in Ruby. A value object is an object whose equality to other objects is determined by its value, not its identity, think dates and amounts of money. A value object should also be immutable, as you don’t want the date “2013-04-22” itself to change but the current date to change from “2013-04-22” to “2013-04-23”. That is, you don’t want entries in a calendar for 2013-04-22 to move to 2013-04-23 simply because the current date changes from 2013-04-22 to 2013-04-23. A value object consists of one or more attributes stored in instance variables. Value sets up an #initialize method for you that let’s you set these attributes, as, value objects being immutable, this’ll be your only chance to do so. Value also adds equality checks ‹#==› and ‹#eql?› (which are themselves equivalent), a ‹#hash› method, a nice ‹#inspect› method, and a protected attribute reader for each attribute. You may of course add any additional methods that your value object will benefit from. That’s basically all there’s too it. Let’s now look at using the Value library. § Usage You create value object class by invoking ‹#Value› inside the class (module) you wish to make into a value object class. Let’s create a class that represent points on a plane: class Point Value :x, :y end A ‹Point› is thus a value object consisting of two sub-values ‹x› and ‹y› (the coordinates). Just from invoking ‹#Value›, a ‹Point› object will have a constructor that takes two arguments to set instance variables ‹@x› and ‹@y›, equality checks ‹#==› and ‹#eql?› (which are the same), a ‹#hash› method, a nice ‹#inspect› method, and two protected attribute readers ‹#x› and ‹#y›. We can thus already creat ‹Point›s: origo = Point.new(0, 0) The default of making the attribute readers protected is often good practice, but for a ‹Point› it probably makes sense to be able to access its coordinates: class Point public(*attributes) end This’ll make all attributes of ‹Point› public. You can of course choose to only make certain attributes public: class Point public :x end Note that this public is standard Ruby functionality. Adding a method to ‹Point› is of course also possible and very much Rubyish: class Point def distance(other) Math.sqrt((other.x - x)**2 + (other.y - y)**2) end end For some value object classes you might want to support optional attributes. This is done by providing a default value for the attribute, like so: class Money Value :amount, [:currency, :USD] end Here, the ‹currency› attribute will default to ‹:USD›. You can create ‹Money› via dollars = Money.new(2) but also kronor = Money.new(2, :SEK) All required attributes must come before any optional attributes. Splat attributes are also supported: class List Value :'*elements' end empty = List.new suits = List.new(:spades, :hearts, :diamonds, :clubs) Splat attributes are optional. Finally, block attributes are also available: class Block Value :'&block' end block = Block.new{ |e| e * 2 } Block attributes are optional. Comparison beyond ‹#==› is possible by specifingy the ‹:comparable› option to ‹#Value›, listing one or more attributes that should be included in the comparison: class Vector Value :a, :b, :comparable => :a end Note that equality (‹#==› and ‹#eql?›) is always defined based on all attributes, regardless of arguments to ‹:comparable›. Here we say that comparisons between ‹Vector›s should be made between the values of the ‹a› attribute only. We can also make comparisons between all attributes of a value object: class Vector Value :a, :b, :comparable => true end To sum things up, let’s use all possible arguments to ‹#Value› at once: class Method Value :file, :line, [:name, 'unnamed'], :'*args', :'&block', :comparable => [:file, :line] end A ‹Method› consists of file and line information, a possible name, some arguments, possibly a block, and is comparable on the file and line on which they appear. Check out the {full API documentation}¹ for a more explicit description, should you need it or should you want to extend it. ¹ See http://disu.se/software/value/api/ § Financing Currently, most of my time is spent at my day job and in my rather busy private life. Please motivate me to spend time on this piece of software by donating some of your money to this project. Yeah, I realize that requesting money to develop software is a bit, well, capitalistic of me. But please realize that I live in a capitalistic society and I need money to have other people give me the things that I need to continue living under the rules of said society. So, if you feel that this piece of software has helped you out enough to warrant a reward, please PayPal a donation to now@disu.se¹. Thanks! Your support won’t go unnoticed! ¹ Send a donation: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=now%40disu%2ese&item_name=Value § Reporting Bugs Please report any bugs that you encounter to the {issue tracker}¹. ¹ See https://github.com/now/value/issues § Authors Nikolai Weibull wrote the code, the tests, the manual pages, and this README. § Licensing Value is free software: you may redistribute it and/or modify it under the terms of the {GNU Lesser General Public License, version 3}¹ or later², as published by the {Free Software Foundation}³. ¹ See http://disu.se/licenses/lgpl-3.0/ ² See http://gnu.org/licenses/ ³ See http://fsf.org/
== Confidently Build Terminal Apps Rooibos[https://rooibos.run] helps you build interactive terminal applications. Keep your code understandable and testable as it scales. Rooibos handles keyboard, mouse, and async work so you can focus on behavior and user experience. gem install rooibos <i>Currently in beta. APIs may change before 1.0.</i> === Get Started in Seconds rooibos new my_app cd my_app rooibos run That's it. You have a working app with keyboard navigation, mouse support, and clickable buttons. Open <tt>lib/my_app.rb</tt> to make it your own. --- === The Pattern \Rooibos uses Model-View-Update, the architecture behind Elm[https://guide.elm-lang.org/architecture/], Redux[https://redux.js.org/], and {Bubble Tea}[https://github.com/charmbracelet/bubbletea]. State lives in one place. Updates flow in one direction. The runtime handles rendering and runs background work for you. --- === Hello, MVU The simplest \Rooibos app. Press any key to increment the counter. Press <tt>Ctrl</tt>+<tt>C</tt> to quit. require "rooibos" module Counter # Init: How do you create the initial model? Init = -> { 0 } # View: What does the user see? View = -> (model, tui) { tui.paragraph(text: <<~END) } Current count: #{model}. Press any key to increment. Press Ctrl+C to quit. END # Update: What happens when things change? Update = -> (message, model) { if message.ctrl_c? Rooibos::Command.exit elsif message.key? model + 1 end } end Rooibos.run(Counter) That's the whole pattern: Model holds state, Init creates it, View renders it, and Update changes it. The runtime handles everything else. --- === Your First Real Application A file browser in sixty lines. It opens files, navigates directories, handles errors, styles directories and hidden files differently, and supports vim-style keyboard shortcuts. If you can do this much with this little code, imagine how easy _your_ app will be to build. require "rooibos" module FileBrowser # Model: What state does your app need? Model = Data.define(:path, :entries, :selected, :error) Init = -> { path = Dir.pwd entries = Entries[path] Ractor.make_shareable( # Ensures thread safety Model.new(path:, entries:, selected: entries.first, error: nil)) } View = -> (model, tui) { tui.block( titles: [model.error || model.path, { content: KEYS, position: :bottom, alignment: :right}], borders: [:all], border_style: if model.error then tui.style(fg: :red) else nil end, children: [tui.list(items: model.entries.map(&ListItem[model, tui]), selected_index: model.entries.index(model.selected), highlight_symbol: "", highlight_style: tui.style(modifiers: [:reversed]))] ) } Update = -> (message, model) { return model.with(error: ERROR) if message.error? model = model.with(error: nil) if model.error && message.key? if message.ctrl_c? || message.q? then Rooibos::Command.exit elsif message.home? || message.g? then model.with(selected: model.entries.first) elsif message.end? || message.G? then model.with(selected: model.entries.last) elsif message.up_arrow? || message.k? then Select[:-, model] elsif message.down_arrow? || message.j? then Select[:+, model] elsif message.enter? then Open[model] elsif message.escape? then Navigate[File.dirname(model.path), model] end } private # Lines below this are implementation details KEYS = "↑/↓/Home/End: Select | Enter: Open | Esc: Navigate Up | q: Quit" ERROR = "Sorry, opening the selected file failed." ListItem = -> (model, tui) { -> (name) { modifiers = name.start_with?(".") ? [:dim] : [] fg = :blue if name.end_with?("/") tui.list_item(content: name, style: tui.style(fg:, modifiers:)) } } Select = -> (operator, model) { new_index = model.entries.index(model.selected).public_send(operator, 1) model.with(selected: model.entries[new_index.clamp(0, model.entries.length - 1)]) } Open = -> (model) { full = File.join(model.path, model.selected.delete_suffix("/")) model.selected.end_with?("/") ? Navigate[full, model] : Rooibos::Command.open(full) } Navigate = -> (path, model) { entries = Entries[path] model.with(path:, entries:, selected: entries.first, error: nil) } Entries = -> (path) { Dir.children(path).map { |name| File.directory?(File.join(path, name)) ? "#{name}/" : name }.sort_by { |name| [name.end_with?("/") ? 0 : 1, name.downcase] } } end Rooibos.run(FileBrowser) --- === Batteries Included ==== Commands Applications fetch data, run shell commands, and set timers. \Rooibos Commands run off the main thread and send results back as messages. <b>HTTP requests:</b> Update = -> (message, model) { case message in :fetch_users [model.with(loading: true), Rooibos::Command.http(:get, "/api/users", :got_users)] in { type: :http, envelope: :got_users, status: 200, body: } model.with(loading: false, users: JSON.parse(body)) in { type: :http, envelope: :got_users, status: } model.with(error: "HTTP #{status}") end } <b>Shell commands:</b> Update = -> (message, model) { case message in :list_files Rooibos::Command.system("ls -la", :listed_files) in { type: :system, envelope: :listed_files, stdout:, status: 0 } model.with(files: stdout.lines.map(&:chomp)) in { type: :system, envelope: :listed_files, stderr:, status: } model.with(error: stderr) end } <b>Timers:</b> Update = -> (message, model) { case message in { type: :timer, envelope: :tick, elapsed: } [model.with(frame: model.frame + 1), Rooibos::Command.wait(1.0 / 24, :tick)] end } <b>And more!</b> \Rooibos includes <tt>all</tt>, <tt>batch</tt>, <tt>bubble</tt>, <tt>cancel</tt>, <tt>custom</tt>, <tt>deliver</tt>, <tt>exit</tt>, <tt>http</tt>, <tt>map</tt>, <tt>open</tt>, <tt>system</tt>, <tt>tick</tt>, and <tt>wait</tt> commands. You can also define your own custom commands for complex orchestration. Every command produces a message, and Update handles it the same way. ==== Testing \Rooibos makes TUIs so easy to test, you'll save more time by writing tests than by not testing. <b>Unit test Update, View, and Init.</b> No terminal needed. Test helpers included. def test_moves_selection_down_with_j model = Ractor.make_shareable(FileBrowser::Model.new( path: "/", entries: %w[bin exe lib], selected: "bin", error: nil)) message = RatatuiRuby::Event::Key.new(code: "j") result = FileBrowser::Update.call(message, model) assert_equal "exe", result.selected end <b>Style assertions.</b> Draw to a headless terminal, verify colors and modifiers. def test_directories_are_blue with_test_terminal(60, 10) do model = Ractor.make_shareable(FileBrowser::Model.new( path: "/", entries: %w[file.txt subdir/], selected: "file.txt", error: nil)) widget = FileBrowser::View.call(model, RatatuiRuby::TUI.new) RatatuiRuby.draw { |frame| frame.render_widget(widget, frame.area) } assert_blue(1, 2) # "subdir/" at column 1, row 2 end end <b>System tests.</b> Inject events, run the full app, snapshot the result. def test_selection_moves_down with_test_terminal(120, 30) do Dir.mktmpdir do |dir| FileUtils.touch(File.join(dir, "a")) FileUtils.touch(File.join(dir, "b")) FileUtils.touch(File.join(dir, "c")) inject_key(:down) inject_key(:ctrl_c) # Tests use explicit params to inject deterministic initial state. Rooibos.run( model: Ractor.make_shareable(FileBrowser::Model.new( path: dir, entries: %w[a b c], selected: "a", error: nil)), view: FileBrowser::View, update: FileBrowser::Update ) assert_snapshots("selection_moved_down") do |lines| title = "┌/tmp/test#{'─' * 107}┐" lines.map do |l| l.gsub(/┌#{Regexp.escape(dir)}[^┐]*┐/, title) end end end end end Snapshots record both plain text and ANSI colors. Normalization blocks mask dynamic content (timestamps, temp paths) for cross-platform reproducibility. Run <tt>UPDATE_SNAPSHOTS=1 rake test</tt> to regenerate baselines. ==== Scale Up Large applications decompose into fragments. Each fragment has its own Model, View, Update, and Init. Parents compose children. The pattern scales. The Router DSL eliminates boilerplate: module Dashboard include Rooibos::Router route :stats, to: StatsPanel route :network, to: NetworkPanel receive_events :ctrl_c, -> { Rooibos::Command.exit } only when: -> (_message, model) { !model.modal_open } do receive_events :q, -> { Rooibos::Command.exit } forward_events :s, to: :stats, as: :fetch forward_events :p, to: :network, as: :ping end Update = from_router # ... Model, Init, View below end Declare routes and event handlers. The router generates Update for you. Use guards to ignore messages when needed. ==== CLI The <tt>rooibos</tt> command scaffolds projects and runs applications. rooibos new my_app # Generate project structure rooibos run # Run the app in current directory Generated apps include tests, type signatures, and a working welcome screen with keyboard and mouse support. --- === The Ecosystem \Rooibos builds on RatatuiRuby[https://www.ratatui-ruby.dev], a Rubygem built on Ratatui[https://ratatui.rs]. You get native performance with the joy of Ruby. \Rooibos is one way to manage state and composition. Kit is another. ==== Rooibos[https://www.rooibos.run] Model-View-Update architecture. Inspired by Elm, Bubble Tea, and React + Redux. Your UI is a pure function of state. - Functional programming with MVU - Commands work off the main thread - Messages, not callbacks, drive updates ==== {Kit}[https://sr.ht/~kerrick/ratatui_ruby/#chapter-3-the-object-path--kit] (Coming Soon) Component-based architecture. Encapsulate state, input handling, and rendering in reusable pieces. - OOP with stateful components - Separate UI state from domain logic - Built-in focus management & click handling Both use the same widget library and rendering engine. Pick the paradigm that fits your brain. --- === Links [Get Started] {Getting Started}[https://www.rooibos.run/docs/trunk/doc/getting_started/index_md.html], {Tutorial}[https://www.rooibos.run/docs/trunk/doc/tutorial/index_md.html], {Examples}[https://www.rooibos.run/docs/trunk/examples/app_fractal_dashboard/README_md.html] [Coming From...] {React/Redux}[https://www.rooibos.run/docs/trunk/doc/getting_started/for_react_developers_md.html], {BubbleTea}[https://www.rooibos.run/docs/trunk/doc/getting_started/for_go_developers_md.html], {Textual}[https://www.rooibos.run/docs/trunk/doc/getting_started/for_python_developers_md.html] [Learn More] {Essentials}[https://www.rooibos.run/docs/trunk/doc/essentials/index_md.html], {Scaling Up}[https://www.rooibos.run/docs/trunk/doc/scaling_up/index_md.html], {Best Practices}[https://www.rooibos.run/docs/trunk/doc/best_practices/index_md.html], {Troubleshooting}[https://www.rooibos.run/docs/trunk/doc/troubleshooting/index_md.html] [Community] {Forum}[https://forum.setdef.com/c/rooibos], {Announcements}[https://forum.setdef.com/tags/c/rooibos/announcement], {Bug Tracker}[https://forum.setdef.com/tags/c/rooibos/bug], {Contribution Guide}[https://github.com/setdef/Rooibos/blob/trunk/CONTRIBUTING.md], {Code of Conduct}[https://github.com/setdef/Rooibos/blob/trunk/CODE_OF_CONDUCT.md] --- [Website] https://rooibos.run [Source] https://github.com/setdef/Rooibos [RubyGems] https://rubygems.org/gems/rooibos © 2026 Kerrick Long · Library: LGPL-3.0-or-later · Website: CC-BY-NC-ND-4.0 · Snippets: MIT-0
== README.md: #ScheduledResource This gem is for displaying how things are used over time -- a schedule for a set of "resources". You can configure the elements of the schedule and there are utilities and protocols to connect them: - Configuration (specification and management), - Query interfaces (a REST-like API and internal protocols to query the models), and - A basic Rails controller implementation. We have a way to configure the schedule, internal methods to generate the data, and a way to retrieve data from the client. However this gem is largely view-framework agnostic. We could use a variety of client-side packages or even more traditional Rails view templates to generate HTML. In any case, to get a good feel in a display like this we need some client-side code. The gem includes client-side modules to: - Manage <b>time and display geometries</b> with "infinite" scroll along the time axis. - <b>Format display cells</b> in ways specific to the resource models. - <b>Update text justification</b> as the display is scrolled horizontally. ## Configuration A **scheduled resource** is something that can be used for one thing at a time. So if "Rocky & Bullwinkle" is on channel 3 from 10am to 11am on Saturday, then 'channel 3' is the <u>resource</u> and that showing of the episode is a <u>resource-use</u> block. Resources and use-blocks are typically Rails models. Each resource and its use-blocks get one row in the display. That row has a label to the left with some timespan visible on the rest of the row. Something else you would expect see in a schedule would be headers and labels -- perhaps one row with the date and another row with the hour. Headers and labels also fit the model of resources and use-blocks. Basic timezone-aware classes (ZTime*) for those are included in this gem. ### Config File The schedule configuration comes from <tt>config/resource_schedule.yml</tt> which has three top-level sections: - ResourceKinds: A hash where the key is a Resource and the value is a UseBlock. (Both are class names), - Resources: A list where each item is a Resource Class followed by one or more resource ids, and - visibleTime: The visible timespan of the schedule in seconds. The example file <tt>config/resource_schedule.yml</tt> (installed when you run <tt>schedulize</tt>) should be enough to display a two-row schedule with just the date above and the hour below. Of course you can monkey-patch or subclass these classes for your own needs. ### The schedule API The 'schedule' endpoint uses parameters <tt>t1</tt> and <tt>t2</tt> to specify a time interval for the request. A third parameter <tt>inc</tt> allows an initial time window to be expanded without repeating blocks that span those boundaries. The time parameters _plus the configured resources_ define the data to be returned. ### More About Configuration Management The <b>ScheduledResource</b> class manages resource and use-block class names, id's and labels for a schedule according to the configuration file. A ScheduledResource instance ties together: 1. A resource class (eg TvStation), 2. An id (a channel number in this example), and 3. Strings and other assets that will go into the DOM. The id is used to - select a resource _instance_ and - select instances of the _resource use block_ class (eg Program instances). The id _could_ be a database id but more often is something a little more suited to human use in the configuration. In any case it is used by model class method <tt>(resource_use_block_class).get_all_blocks()</tt> to select the right use-blocks for the resource. A resource class name and id are are joined with a '_' to form a tag that also serves as an id for the DOM. Once the configuration yaml is loaded that data is maintained in the session structure. Of course having a single configuration file limits the application's usefulness. A more general approach would be to have a user model with login and configuration would be associated with the user. ## Installation Add this line to your application's Gemfile: ```ruby gem 'scheduled_resource' ``` And then execute: $ bundle Or install it yourself as: $ gem install scheduled_resource Then from your application's root execute: $ schedulize . This will install a few image placeholders, client-side modules and a stylesheet under <tt>vendor/assets</tt>, an example configuration in <tt>config/resource_schedule.yml</tt> and an example controller in <tt>app/controllers/schedule_controller.rb</tt>. Also, if you use $ bundle show scheduled_resource to locate the installed source you can browse example classes <tt>lib/z_time_*.rb</tt> and the controller helper methods in <tt>lib/scheduled_resource/helper.rb</tt> ## Testing This gem also provides for a basic test application using angularjs to display a minimal but functional schedule showing just the day and hour headers in two different timezones (US Pacific and Eastern). Proceed as follows, starting with a fresh Rails app: $ rails new test_sr As above, add the gem to the Gemfile, then $ cd test_sr $ bundle $ schedulize . Add lines such as these to <tt>config/routes.rb</tt> get "/schedule/index" => "schedule#index" get "/schedule" => "schedule#schedule" Copy / merge these files from the gem source into the test app: $SR_SRC/app/views/layouts/application.html.erb $SR_SRC/app/views/schedule/index.html.erb $SR_SRC/app/assets/javascripts/{angular.js,script.js,controllers.js} and add <tt>//= require angular</tt> to application.js just below the entries for <tt>jquery</tt>. After you run the server and browse to http://0.0.0.0:3000/schedule/index you should see the four time-header rows specified by the sample config file. ## More Examples A better place to see the use of this gem is at [tv4](https://github.com/emeyekayee/tv4). Specifically, models <tt>app/models/event.rb</tt> and <tt>app/models/station.rb</tt> give better examples of implementing the ScheduledResource protocol and adapting to a db schema organized along somewhat different lines. ## Contributing 1. Fork it ( https://github.com/emeyekayee/scheduled_resource/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request
# Rake::ToolkitProgram Create toolkit programs easily with `Rake` and `OptionParser` syntax. Bash completions and usage help are baked in. ## Installation Add this line to your application's Gemfile: ```ruby gem 'rake-toolkit_program' ``` And then execute: $ bundle Or install it yourself as: $ gem install rake-toolkit_program ## Quickstart * Shebang it up (in a file named `awesome_tool.rb`) ```ruby #!/usr/bin/env ruby ``` * Require the library ```ruby require 'rake/toolkit_program' ``` * Make your life easier ```ruby Program = Rake::ToolkitProgram ``` * Define your command tasks ```ruby Program.command_tasks do desc "Build it" task 'build' do # Ruby code here end desc "Test it" task 'test' => ['build'] do # Rake syntax ↑↑↑↑↑↑↑ for dependencies # Ruby code here end end ``` You can use `Program.args` in your tasks to access the other arguments on the command line. For argument parsing integrated into the help provided by the program, see the use of `Rake::Task(Rake::ToolkitProgram::TaskExt)#parse_args` below. * Wire the mainline ```ruby Program.run(on_error: :exit_program!) if $0 == __FILE__ ``` * In the shell, prepare to run the program (UNIX/Linux systems only) ```console $ chmod +x awesome_tool.rb $ ./awesome_tool.rb --install-completions Completions installed in /home/rtweeks/.bashrc Source /home/rtweeks/.bash-complete/awesome_tool.rb-completions for immediate availability. $ source /home/rtweeks/.bash-complete/awesome_tool.rb-completions ``` * Ask for help ```console $ ./awesome_tool.rb help *** ./awesome_tool.rb Toolkit Program *** . . . ``` ## Usage Let's look at a short sample toolkit program -- put this in `awesome.rb`: ```ruby #!/usr/bin/env ruby require 'rake/toolkit_program' require 'ostruct' ToolkitProgram = Rake::ToolkitProgram ToolkitProgram.title = "My Awesome Toolkit of Awesome" ToolkitProgram.command_tasks do desc <<-END_DESC.dedent Fooing myself I'm not sure what I'm doing, but I'm definitely fooing! END_DESC task :foo do a = ToolkitProgram.args puts "I'm fooed#{' on a ' if a.implement}#{a.implement}" end.parse_args(into: OpenStruct.new) do |parser, args| parser.no_positional_args! parser.on('-i', '--implement IMPLEMENT', 'An implement on which to be fooed') do |val| args.implement = val end end end if __FILE__ == $0 ToolkitProgram.run(on_error: :exit_program!) end ``` Make sure to `chmod +x awesome.rb`! What does this support? $ ./awesome.rb foo I'm fooed $ ./awesome.rb --help *** My Awesome Toolkit of Awesome *** Usage: ./awesome.rb COMMAND [OPTION ...] Avaliable options vary depending on the command given. For details of a particular command, use: ./awesome.rb help COMMAND Commands: foo Fooing myself help Show a list of commands or details of one command Use help COMMAND to get more help on a specific command. $ ./awesome.rb help foo *** My Awesome Toolkit of Awesome *** Usage: ./awesome.rb foo [OPTION ...] Fooing myself I'm not sure what I'm doing, but I'm definitely fooing! Options: -i, --implement IMPLEMENT An implement on which to be fooed $ ./awesome.rb --install-completions Completions installed in /home/rtweeks/.bashrc Source /home/rtweeks/.bash-complete/awesome.rb-completions for immediate availability. $ source /home/rtweeks/.bash-complete/awesome.rb-completions $ ./awesome.rb <tab><tab> foo help $ ./awesome.rb f<tab> ↳ ./awesome.rb foo $ ./awesome.rb foo <tab> ↳ ./awesome.rb foo -- $ ./awesome.rb foo --<tab><tab> --help --implement $ ./awesome.rb foo --i<tab> ↳ ./awesome.rb foo --implement $ ./awesome.rb foo --implement <tab><tab> --help awesome.rb $ ./awesome.rb foo --implement spoon I'm fooed on a spoon ### Defining Toolkit Commands Just define tasks in the block of `Rake::ToolkitProgram.command_tasks` with `task` (i.e. `Rake::DSL#task`). If `desc` is used to provide a description, the task will become visible in help and completions. When a command task is initially defined, positional arguments to the command are available as an `Array` through `Rake::ToolkitProgram.args`. ### Option Parsing This gem extends `Rake::Task` with a `#parse_args` method that creates a `Rake::ToolkitProgram::CommandOptionParser` (derived from the standard library's `OptionParser`) and an argument accumulator and `yield`s them to its block. * The arguments accumulated through the `Rake::ToolkitProgram::CommandOptionParser` are available to the task in `Rake::ToolkitProgram.args`, replacing the normal `Array` of positional arguments. * Use the `into:` keyword of `#parse_args` to provide a custom argument accumulator object for the associated command. The default argument accumulator constructor can be defined with `Rake::ToolkitProgram.default_parsed_args`. Without either of these, the default accumulator is a `Hash`. * Options defined using `OptionParser#on` (or any of the variants) will print in the help for the associated command. ### Positional Arguments Accessing positional arguments given after the command name depends on whether or not `Rake::Task(Rake::ToolkitProgram::TaskExt)#parse_args` has been called on the command task. If this method is not called, positional arguments will be an `Array` accessible through `Rake::ToolkitProgram.args`. When `Rake::Task(Rake::ToolkitProgram::TaskExt)#parse_args` is used: * `Rake::ToolkitProgram::CommandOptionParser#capture_positionals` can be used to define how positional arguments are accumulated. * If the argument accumulator is a `Hash`, the default (without calling this method) is to assign the `Array` of positional arguments to the `nil` key of the `Hash`. * For other types of accumulators, the positional arguments are only accessible if `Rake::ToolkitProgram::CommandOptionParser#capture_positionals` is used to define how they are captured. * If a block is given to this method, the block of the method will receive the `Array` of positional arguments. If it is passed an argument value, that value is used as the key under which to store the positional arguments if the argument accumulator is a `Hash`. * `Rake::ToolkitProgram::CommandOptionParser#expect_positional_cardinality` can be used to set a rule for the count of positional arguments. This will affect the _usage_ presented in the help for the associated command. * `Rake::ToolkitProgram::CommandOptionParser#map_positional_args` may be used to transform (or otherwise process) positional arguments one at a time and in the context of options and/or arguments appearing earlier on the command line. ### Convenience Methods * `Rake::Task(Rake::ToolkitProgram::TaskExt)#prohibit_args` is a quick way, for commands that accept no options or positional arguments, to declare this so the help and bash completions reflect this. It is equivalent to using `#parse_args` and telling the parser `parser.expect_positional_cardinality(0)`. * `Rake::ToolkitProgram::CommandOptionParser#no_positional_args!` is a shortcut for calling `#expect_positional_cardinality(0)` on the same object. * `Rake::Task(Rake::ToolkitProgram::TaskExt)#invalid_args!` and `Rake::ToolkitProgram::CommandOptionParser#invalid_args!` are convenient ways to raise `Rake::ToolkitProgram::InvalidCommandLine` with a message. ## OptionParser in Rubies Before and After v2.4 The `OptionParser` class was extended in Ruby 2.4 to simplify capturing options into a `Hash` or other container implementing `#[]=` in a similar way. This gem supports that, but it means that behavior varies somewhat between the pre-2.4 era and the 2.4+ era. To have consistent behavior across that version change, the recommendation is to use a `Struct`, `OpenStruct`, or custom class to hold program options rather than `Hash`. ## Development After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). To run the tests, use `rake`, `rake test`, or `rspec spec`. Tests can only be run on systems that support `Kernel#fork`, as this is used to present a pristine and isolated environment for setting up the tool. If run using Ruby 2.3 or earlier, some tests will be pending because functionality expects Ruby 2.4's `OptionParser`. ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/PayTrace/rake-toolkit_program. For further details on contributing, see [CONTRIBUTING.md](./CONTRIBUTING.md).
= Simple task organizer syctask can be used to create, plan, prioritize and schedule tasks. ==Install The application can be installed with $ gem install syc-task == Usage syctask provides basic task organizer functions as create, update, list and complete a task. Additional functions are to plan tasks you want to accomplish today. If you are not sure in which sequence to conduct the task you can prioritize them with a pair wise comparisson. You can time tasks with start and stop and you can finally extract tasks from a minutes of meetings file. The schedule task command will print a graphical timeline of the working day assigning the planned tasks to the timeline. Busy times are marked red. Meetings are listed with associated tasks that are assigned to the meetings. With the statistics command you can print statistical evaluation of tasks duration and count. ===Create tasks with new Create a new task in the default task directory ~/.tasks $ syctask new "My first task" Provide a description $ syctask new "My first task" --description "Explanation of my first task" Schedule a task with a follow-up and due date $ syctask new "My first task" --follow-up "2013-02-25" --due "2013-03-11" Set a proirity for a task $ syctask new "My first task" --prio 3 Prompt for task input $ syctask new will prompt for task titles. Ctrl-D will end input. Except for --description you can also provide short forms for the options. ===Create tasks by scanning from files When writing minutes of meetings tasks that should be followed up in syctask can be annotated so they will be recognized by the scan command. The following structure shows how to annotade tasks Some text before @task; title;description;follow_up;due_date,prio Schedule meeting;Invite all developers;2016-09-12;2016-10-12;1 Write letter;Practice writing letters;;;3 Some text after The above annotation will only scan the next task because of the singular 'task' where the task values are separated with ';'. The line after the annotation '@task' lists the sequence of the fields of the task. It is also possible to list the tasks in a table, e.g. markdown Some text before @tasks| title |description |follow_up |due_date |prio ----------------|--------------------------|----------|----------|---- Schedule meeting|Invite all developers |2016-09-12|2016-10-12|1 Write letter |Practice writing letters | | |3 Some text after Call partner |Ask for project's progress|2016-09-14| |1 Even more text The example above scans all tasks due to the plural 'tasks'. It also scans all tasks that are separated with non-task text and occur after the annotation and confirm to the field structure. Lines that start with '-' will be ignored. So if you want to skip only a few tasks within a task list prepend them with '-'. If you have tasks with different fields then you have to add another annotation with the new field structure. Possible fields are title - the title of the task - mandatory field! description - the description of the task follow_up - the follow-up date of the task in the form yyyy-mm-dd due_date - the due-date of the task in the form yyyy-mm-dd prio - the priority of the task tags - tags the task is annotated with note - a note for the task Note: follow_up and due_date can also be written as Follow-up and Due-Date. Also case is ignored. As inidcated in the list the title column is mandatory. Without the title column scan will raise an error during a scan. Fields that are not part of the above list will be ignored. # | Title | Who - | ------------------------------------ | --- 1 | Schedule meeting with all developers | Me 2 | Write letter to practice writing | You In the table only the column Title will be scanned. The '#' and 'Who' column will be ignored during scan. This table is also a table for a minimum scan structure. You need at least to provide a title column so the scan function will recognize the table as a task list. Scanning tasks from files $ syctask scan 2016-09-10-mom.md 2016-09-09-mom.md ===Plan tasks The plan command will print tasks and prompts whether to (a)dd or (s)kip the task. If (q)uit is selected the tasks already added will be add to the today's task list. If (c)omplete is selected the complete task will be printed and the user will be prompted again for adding the task. Invoke plan without filter $ syctask plan 1 - My first task (a)dd, (c)omplete, (s)kip, (q)uit? a Duration (1 = 15 minutes, return 30 minutes): 3 --> 1 task(s) planned Invoke plan with a filter $ syctask plan --id "1,3,5,8" 1 - My first task (a)dd, (c)omplete, (s)kip, (q)uit? Move tasks to another days plan $ syctask plan today --move tomorrow --id 3,5 This will move the tasks with ID 3 and 5 from the today's plan to the tomorrow's plan. The duration will be set to the remaining processing time but at least to 30 minutes. ===Prioritize tasks Planned tasks can be prioritized in a pair wise comparisson. So each task is compared to all other tasks. The task with the highest priority will bubble on top followed by the task with the next highest priority and so on. $ syctask prio 1: My first task 2: My second task Task 1 has (h)igher or (l)ower priority, or (q)uit: h 1: My first task 2: My third task Task 1 has (h)igher or (l)ower priority, or (q)uit: l 1: My third task 2: My fourth task Task 1 has (h)igher or (l)ower priority, or (q)uit: h ... syctask schedule will then print tasks as follows Tasks ----- 0: 10 - My fourth task 1: 7 - My third task 2: 3 - My first task 3: 9 - My second task ... Instead of conducting pairwise comparisson the order of the tasks in the plan can be specified with the -o flag $ syctask plan -o 7,3,10,9 The plan or schedule command will print the tasks in the specified order Tasks ----- 0: 7 - My third task 1: 3 - My first task 2: 10 - My fourth task 3: 9 - My second task If only a part of the tasks is provided the rest of the tasks is appended to the end of the task plan. If you specify a position flag the prioritized tasks are added at the provided position. $ syctask plan -o 7,9 -p 2 Tasks ----- 0: 3 - My first task 1: 10 - My fourth task 2: 7 - My third task 3: 9 - My second task ===Create schedule The schedule command will print a graphical schedule with assigning the tasks selected with plan. When schedule command is invoked the planned tasks are added at or after the current time within the time schedule. Tasks that are done and scheduled in the future are not shown. Tasks done and in the past are shown with the actual processing time. The day starts at 00:00 and ends at 23:59. So 24:00 should be 00:00. Create a schedule with working time from 8a.m. to 6p.m. and meetings between 9a.m. and 9.30a.m. and 1p.m. and 2.45p.m. $ syctask schedule -w "8:00-18:00" -b "9:00-9:30,13:00-14:45" Add titles to the meetings $ syctask schedule -m "Project status,Management meeting" The output will be Meetings -------- A - Project status B - Management meeting A B xxx-///-|---|---|---///////-|---|---|---| 8 9 10 11 12 13 14 15 16 17 18 1 Tasks ----- 0 - 1: My first task Adding a task to a meeting $ syctask schedule -a "A:0" will print Meetings -------- A - Project status 1 - My first task B - Management meeting A B ----///-|---|---|---///////-|---|---|---| 8 9 10 11 12 13 14 15 16 17 18 Tasks ----- 0: 1 - My first task A task that is re-scheduled with $ syctask update 1 -f tomorrow will be shown as done (green) in the schedule and instead of separator - it shows ~. Tasks ---- 0: 1 ~ My first task A started task will be indicated by * $ syctask start 1 $ syctask sche Tasks ----- 0: 1 * My first task ===List tasks List tasks that are not marked as done in short form $ syctask list List all tasks in long form $ syctask list --all --complete Search tasks that match a pattern $ syctask list --id "<10" --follow_up ">2013-02-25" --title "My \w task" ===Inspect tasks Lists each unplanned task and allows to edit, delete, mark as done or plan for today or another day $ syctask inspect 0016 Create command for inspection (e)dit, (d)one, de(l)ete, (p)lan, da(t)e, (c)omplete, (s)kip, (b)ack, (q)uit ===Edit task Edit a task with ID 10 in vi $ syctask edit 10 ===Update tasks Except for title and id all values can be updated. Note and tags are not overridden rather supplemented with the update value. Update task with ID 1 and provide some informative note $ syctask update 1 --note "Some explanation about the progress on the task" ===Complete tasks Complete the task with ID 1 and provide a final note $ syctask done 1 --note "Finalize my first task" ===Delete tasks Delete tasks with ID 1,3 and 5 from the default task directory $ syctask delete --id 1,3,5 Delete tasks with ID 8 and 12 from the planned tasks of today. The tasks are only removed from the planned tasks and not physically deleted. $ syctask delete --plan today --id 8,12 ===Settings The settings command allows to define default values for task directory and to create general purpose tasks that can be used for tracking and later statistical evaluation. Create general purpose tasks for phone and talk $ syctask setting --general PHONE,TALK List all settings $ syctask setting --list ===Info Info searches for the location of a task and lists all task directories Search for task with id 102 $ syctask info --id 102 List all task directories $ syctask info --taskdir ===Statistics Shows statistics for work and meeting times as well as for task processing Evaluate the complete log file $ syctask statistics Evaluate work times, meetings and tasks between 2013-01-01 and 2013-04-14 $ syctask statistics 2013-01-01 2013-04-14 Evaluate yesterday and today $ syctask statistics yesterday today ===Task directory and project directory The global options --taskdir and --project determine where the command finds or creates the tasks. The default task directory is ~/.tasks, so if no task directory is specified all commands obtain tasks from or create tasks in ~/.tasks. If a project is specified the tasks will be saved to or obtained from the task directories subdirectory specified with the --project flag. --taskdir --project Tasks in - - default_task_dir x - task_dir - x default_task_dir/project x x task_dir/project In the table the relation of commands to --taskdir and --project are listed. Command --taskdir --project Comment delete x x deletes the tasks in taskdir/project done x x marks tasks in taskdir/project as done help - - inspect x x lists task to edit, done, delete, plan list x x lists tasks in taskdir/project new x x creates tasks in taskdir/project plan x x retrieves tasks to plan from taskdir/projekt prio - - input to prio are planned tasks (see plan) scan x x creates scanned tasks in taskdir/project schedule - - schedules the planned tasks (see plan) start - - starts task from planned tasks (see plan) statistics - - shows statistics of time and count stop - - stops task from planned task update x x updates task in taskdir/project ===Files * ID id file contains the last issued id. * IDS ids file contains all issued ids. * Task files The tasks are named ID.task where ID is any Integer as 10.task. The files are saved as YAML files and can be edited directly. * Planned tasks files The planned tasks are save to YYYY-MM-DD_planned_tasks in syctask's system directory. Each task is saved with the task's directory and the ID. * Schedule files The schedule is saved to YYYY-MM-DD_time_schedule in the default task directory. The files are saved as YAML files and can be changed manually. * Log file Creating schedule and task processings is logged to tasks.log. For example when a task is started and stopped this is action is saved to tasks.log. * Tracked file A started task is saved to tracked_tasks. A semaphore file is created with ID.track when the task ID is started. When the task is stopped the semaphore file is deleted. * General purpose tasks With syctask setting -g PHONE so called general purpose tasks can be created. These tasks can be used for time tracking and later statistic evaluation to determine the amount of disturbences e.g. by phone. These tasks are saved to default_tasks. The general purpose tasks itself are also saved to the .syc/syctask directory as regular task files. * Default task dir The default task that is used e.g. with list is saved to default_tasks_dir. This can be set with the setting command. ==Working with syctask To work with syctask and get the most out of it there is to follow a certain process. ===Creating a schedule ==== View tasks In the morning before I start to work I scan my tasks with syctask list or syctask inspect to get an overview of my open tasks. $ syctask list ==== Plan tasks Next I start the planning phase with syctask plan. If I have a specific schedule for the day I will filter for the respective tasks $ syctask plan ==== Prioritize tasks (optionally) If I want to process the tasks in a specific sequence I prioritize the tasks with $ syctask prio ==== Create schedule I create a schedule with my working hours and meetings that have been scheduled with $ syctask schedule -w "8:00-18:00" -b "9:00-10:00,14:30-16:00" -m "Team,Status" ==== Create an agenda I assign the topics I want to discuss in the meetings to the meetings with syctask schedule -a "A:1,3,6;B:3,5" ==== Start a task To begin I start the first task in the schedule with syctask start -p ID (where ID is the ID of the planned (-p) tasks) $ syctask start -p 10 ==== End a task To end the task I invoke $ syctask stop This will stop the last started task ==== Re-schedule a task If I cannot finish a task than I update the task with a new follow-up date $ syctask update 23 -f tomorrow The task will be shown in the today's schedule as done. ==== Complete a task When the task is done I call $ syctask done 23 ===Attachements * E-mails If an e-mail creates a task I create a new task with syctask new title_of_task. The subject of the e-mail I prepend with the ID and move the e-mail to a <b>open topics</b> directory. * Files If I create files in the course of a task I create a folder in the task directory with the ID and save the files in this directory. If there is an existing directory I link to the file from the ID directory ==Supported platform syc-task up to version 0.4.2 has been tested with Ruby 1.9.3. Version 0.4.2 also runs with Ruby 2.7. It also works in Windows using Cygwin. Version 1.0.0 has been upgraded to Ruby 3.2. ==Add TAB-completion to syctask To activate bash's TAB-completion following lines have to be added to ~/.bashrc complete -F get_syctask_commands syctask function get_syctask_commands { if [ -z $2 ] ; then COMPREPLY=(`syctask help -c`) else COMPREPLY=(`syctask help -c $2`) fi } After ~/.bashrc has been updated the shell session has to be restarted with $ source ~/.bashrc Now syctask followed by TAB TAB will print $ syctask <TAB><TAB> delete done list plan scan stop _doc help new prio schedule start update To complete a command we can type $ syctask sch<TAB> which will complete to $ syctask schedule ==Output to Printer To print syctask's output to a printer pipe the command to lpr $ syctask schedule | lpr This will print the schedule to the default printer. To determine all available printer lpstat can be used with the lpstat -a command $ lpstat -a Canon-LBP6650-3470 accepting requests since Sat 16 Mar 2013 04:26:15 PM CET Dell-B1160w-Mono accepting requests since Sat 16 Mar 2013 04:27:45 PM CET To print to Dell-B1160w-Mono the following command can be used $ syctask schedule | lpr -P Dell-B1160w-Mono ==Release Notes ===Version 0.0.1 Implementation of new, update, list and done commands. ===Version 0.0.4 * delete: deleting tasks or remove tasks from a task plan * plan: plan tasks and add them to the task plan * schedule: create a schedule with work and busy time and assign the tasks from the task plan to the free times ===Version 0.0.6 * start: start a task and track the lead time * stop: stop the tracking and print the lead time of the task * start, stop: the task is logged in the ~/.tasks/task.log file when added and when stopped * prio: prioritize tasks in the task plan, that is specifying the sequence in that the tasks should be conducted * plan: --move flag added to move tasks from the specified plan to another days task plan * update, new: when a follow-up or a due date is provided the task is added to the provided dates task plan. If both dates are set the task is added to both dates task plans ===Version 0.0.7 * updated rdoc ===Version 0.1.15 * IDs are now unique independent of the task or project directory. After upgrading from a version 0.0.7 or older the user asked whether to re-index the tasks. It is adviced to tar the tasks before re-indexing with $ tar cvfz tasks.tar.gz .tasks other_task_directories * start will now show a timer in the upper right corner of the screen when started with the -t (--timer) flag. $ syctask start 10 -t In order to use the task timer ncurses has to be installed as the task timer uses tput from the ncurses library. * The schedule has a heading with the schedule's date and the working time * Planned tasks are now added at or after the current time if they are not done yet. Done tasks are shown in the past with the actual processing time. Tasks done before the start of the schedule are not shown in the schedule. * Meetings that are at the current time are indicated with a *. Active tasks are indicated with a star, re-scheduled tasks are indicated with a ~. * Assigning tasks to meetings in a schedule is now done with the task ID * Statistics show statistics about work time, meeting times, general purpose tasks and task processing. Total, min, max and average time and count is listed. If you have used version 0.0.7 it is adviced to delete tasks.log that lives in ~/.tasks before upgrading or in ~/.syc/syctask after upgrading. Otherwise the statistic results seem odd. * Meeting time in time line now shows correct duration * Info command searches for the location of a task and lists all task task directories with the tasks contained. * Plan move command sets the duration to the remaining processing time but at least to 15 minutes * With the setting command the default task directory can be set and general purpose tasks can be created. A general purpose task can be used for tracking to analyse how much time for phone calls is occupied. setting -l list all general purpose tasks and the default task directory * Prio command now takes a position flag together with the order flag to determine where to insert the newly ordered tasks * All commands that take an ID as argument (done, edit, start, update) look up the task file associated to the id in the ids file. If it is found the provided task directory is not considered for the task file. If the id is not contained in the ids file the task is looked up in the provided directory * Inspect command allows to list each today's unplanned task to edit, delete, mark as done or plan * Update command now has a duration flag to set the task's duration ====Version 0.2.0 * Migrated from TestUnit to Minitest * Implemented _timeleap_ {<img src="https://badge.fury.io/rb/timeleap.svg" alt="Gem Version" />}[http://badge.fury.io/rb/timeleap] which allows to specify additional time distances to yesterday, today tomorrow. Time distances come in two flavors as long and short forms. Examples for long forms are - yesterday|today|tomorrow - next|previous_monday|tuesday|...|sunday - monday|tuesday|...|sunday_in|back_1_week|month|year - in|back_10_days|weeks|months|years Examples for short forms are - y|tod|tom - n|pmo|tu|..|su - mo|tu|...|sui|b1w|m|y - i|b10d|w|m|y ====Version 0.2.1 * Fix a bug in `syctask delete --plan` * Add indicator '>' to task list when task contains notes * Refactor migration from version 0.0.7 and when user has deleted system files. The user can now specify the directories where the tasks are located and can also define directories to be excluded. This is especially helpful to omit search in large mounted directories, like from NAS servers. ====Version 0.3.1 * Add csv output spearated by ';' to the list command * Fix bug when schedule file is empty * Add scan command to scan tasks from files ====Version 0.3.2 * Fix bugs of missing class lib/syctask/scanner.rb ====Version 0.4.2 * delete command can take now ranges of ids, e.g. 1,2,4-8,5,20-25 * inspect can now go back in the task list * inspect will now show the updated task after making changes to the task in edit * inspect allows to specify a follow_up date * scan will ignore columns that are not part of a syctask task * scan recognizes 'Follow-up' as well as 'follow_up' now. That is an underscore can be replaced with '-' * Fix bug when scanning tables that have spaces between separator and column * When tasks.log file is missing `syctask inspect` prints warning with reason why statistics cannot be printed ====Version 1.0.0 * Upgrade to Ruby 3.2.2 ==Development Pull from Github and then run $ bundle install New classes have to be added to 'lib/syctask.rb' Debugging the interface can be done with GLI_DEBUG: $ bundle exec env GLI_DEBUG=true bin/syctask Building and pushing the gemfile to Rubygems $ gem build syctask.gemspec $ gem push syc-task-0.2.1.gem ==Tests The test files live in the folder test and start with test_. There is a rake file available to run all tests $ rake test The CLI is tested with Cucumber. To run the Cucumber features in verbose mode $ cucumber or if you prefer cleaner output run $ rake features ==License syc-task is released under the {MIT License}[http://opensource.org/licenses/MIT] ==Links * [http://www.github.com/sugaryourcoffee/syc-task] - Source code on GitHub * [https://rubygems.org/gems/syc-task] - RubyGems
Lookout Lookout is a unit testing framework for Ruby¹ that puts your results in focus. Tests (expectations) are written as follows expect 2 do 1 + 1 end expect ArgumentError do Integer('1 + 1') end expect Array do [1, 2, 3].select{ |i| i % 2 == 0 } end expect [2, 4, 6] do [1, 2, 3].map{ |i| i * 2 } end Lookout is designed to encourage – force, even – unit testing best practices such as • Setting up only one expectation per test • Not setting expectations on non-public APIs • Test isolation This is done by • Only allowing one expectation to be set per test • Providing no (additional) way of accessing private state • Providing no setup and tear-down methods, nor a method of providing test helpers Other important points are • Putting the expected outcome of a test in focus with the steps of the calculation of the actual result only as a secondary concern • A focus on code readability by providing no mechanism for describing an expectation other than the code in the expectation itself • A unified syntax for setting up both state-based and behavior-based expectations The way Lookout works has been heavily influenced by expectations², by {Jay Fields}³. The code base was once also heavily based on expectations, based at Subversion {revision 76}⁴. A lot has happened since then and all of the work past that revision are due to {Nikolai Weibull}⁵. ¹ Ruby: http://ruby-lang.org/ ² Expectations: http://expectations.rubyforge.org/ ³ Jay Fields’s blog: http://blog.jayfields.com/ ⁴ Lookout revision 76: https://github.com/now/lookout/commit/537bedf3e5b3eb4b31c066b3266f42964ac35ebe ⁵ Nikolai Weibull’s home page: http://disu.se/ § Installation Install Lookout with % gem install lookout § Usage Lookout allows you to set expectations on an object’s state or behavior. We’ll begin by looking at state expectations and then take a look at expectations on behavior. § Expectations on State: Literals An expectation can be made on the result of a computation: expect 2 do 1 + 1 end Most objects, in fact, have their state expectations checked by invoking ‹#==› on the expected value with the result as its argument. Checking that a result is within a given range is also simple: expect 0.099..0.101 do 0.4 - 0.3 end Here, the more general ‹#===› is being used on the ‹Range›. § Regexps ‹Strings› of course match against ‹Strings›: expect 'ab' do 'abc'[0..1] end but we can also match a ‹String› against a ‹Regexp›: expect %r{a substring} do 'a string with a substring' end (Note the use of ‹%r{…}› to avoid warnings that will be generated when Ruby parses ‹expect /…/›.) § Modules Checking that the result includes a certain module is done by expecting the ‹Module›. expect Enumerable do [] end This, due to the nature of Ruby, of course also works for classes (as they are also modules): expect String do 'a string' end This doesn’t hinder us from expecting the actual ‹Module› itself: expect Enumerable do Enumerable end or the ‹Class›: expect String do String end for obvious reasons. As you may have figured out yourself, this is accomplished by first trying ‹#==› and, if it returns ‹false›, then trying ‹#===› on the expected ‹Module›. This is also true of ‹Ranges› and ‹Regexps›. § Booleans Truthfulness is expected with ‹true› and ‹false›: expect true do 1 end expect false do nil end Results equaling ‹true› or ‹false› are slightly different: expect TrueClass do true end expect FalseClass do false end The rationale for this is that you should only care if the result of a computation evaluates to a value that Ruby considers to be either true or false, not the exact literals ‹true› or ‹false›. § IO Expecting output on an IO object is also common: expect output("abc\ndef\n") do |io| io.puts 'abc', 'def' end This can be used to capture the output of a formatter that takes an output object as a parameter. § Warnings Expecting warnings from code isn’t very common, but should be done: expect warning('this is your final one!') do warn 'this is your final one!' end expect warning('this is your final one!') do warn '%s:%d: warning: this is your final one!' % [__FILE__, __LINE__] end ‹$VERBOSE› is set to ‹true› during the execution of the block, so you don’t need to do so yourself. If you have other code that depends on the value of $VERBOSE, that can be done with ‹#with_verbose› expect nil do with_verbose nil do $VERBOSE end end § Errors You should always be expecting errors from – and in, but that’s a different story – your code: expect ArgumentError do Integer('1 + 1') end Often, not only the type of the error, but its description, is important to check: expect StandardError.new('message') do raise StandardError.new('message') end As with ‹Strings›, ‹Regexps› can be used to check the error description: expect StandardError.new(/mess/) do raise StandardError.new('message') end § Queries Through Symbols Symbols are generally matched against symbols, but as a special case, symbols ending with ‹?› are seen as expectations on the result of query methods on the result of the block, given that the method is of zero arity and that the result isn’t a Symbol itself. Simply expect a symbol ending with ‹?›: expect :empty? do [] end To expect it’s negation, expect the same symbol beginning with ‹not_›: expect :not_nil? do [1, 2, 3] end This is the same as expect true do [].empty? end and expect false do [1, 2, 3].empty? end but provides much clearer failure messages. It also makes the expectation’s intent a lot clearer. § Queries By Proxy There’s also a way to make the expectations of query methods explicit by invoking methods on the result of the block. For example, to check that the even elements of the Array ‹[1, 2, 3]› include ‹1› you could write expect result.to.include? 1 do [1, 2, 3].reject{ |e| e.even? } end You could likewise check that the result doesn’t include 2: expect result.not.to.include? 2 do [1, 2, 3].reject{ |e| e.even? } end This is the same as (and executes a little bit slower than) writing expect false do [1, 2, 3].reject{ |e| e.even? }.include? 2 end but provides much clearer failure messages. Given that these two last examples would fail, you’d get a message saying “[1, 2, 3]#include?(2)” instead of the terser “true≠false”. It also clearly separates the actual expectation from the set-up. The keyword for this kind of expectations is ‹result›. This may be followed by any of the methods • ‹#not› • ‹#to› • ‹#be› • ‹#have› or any other method you will want to call on the result. The methods ‹#to›, ‹#be›, and ‹#have› do nothing except improve readability. The ‹#not› method inverts the expectation. § Literal Literals If you need to literally check against any of the types of objects otherwise treated specially, that is, any instances of • ‹Module› • ‹Range› • ‹Regexp› • ‹Exception› • ‹Symbol›, given that it ends with ‹?› you can do so by wrapping it in ‹literal(…)›: expect literal(:empty?) do :empty? end You almost never need to do this, as, for all but symbols, instances will match accordingly as well. § Expectations on Behavior We expect our objects to be on their best behavior. Lookout allows you to make sure that they are. Reception expectations let us verify that a method is called in the way that we expect it to be: expect mock.to.receive.to_str(without_arguments){ '123' } do |o| o.to_str end Here, ‹#mock› creates a mock object, an object that doesn’t respond to anything unless you tell it to. We tell it to expect to receive a call to ‹#to_str› without arguments and have ‹#to_str› return ‹'123'› when called. The mock object is then passed in to the block so that the expectations placed upon it can be fulfilled. Sometimes we only want to make sure that a method is called in the way that we expect it to be, but we don’t care if any other methods are called on the object. A stub object, created with ‹#stub›, expects any method and returns a stub object that, again, expects any method, and thus fits the bill. expect stub.to.receive.to_str(without_arguments){ '123' } do |o| o.to_str if o.convertable? end You don’t have to use a mock object to verify that a method is called: expect Object.to.receive.name do Object.name end As you have figured out by now, the expected method call is set up by calling ‹#receive› after ‹#to›. ‹#Receive› is followed by a call to the method to expect with any expected arguments. The body of the expected method can be given as the block to the method. Finally, an expected invocation count may follow the method. Let’s look at this formal specification in more detail. The expected method arguments may be given in a variety of ways. Let’s introduce them by giving some examples: expect mock.to.receive.a do |m| m.a end Here, the method ‹#a› must be called with any number of arguments. It may be called any number of times, but it must be called at least once. If a method must receive exactly one argument, you can use ‹Object›, as the same matching rules apply for arguments as they do for state expectations: expect mock.to.receive.a(Object) do |m| m.a 0 end If a method must receive a specific argument, you can use that argument: expect mock.to.receive.a(1..2) do |m| m.a 1 end Again, the same matching rules apply for arguments as they do for state expectations, so the previous example expects a call to ‹#a› with 1, 2, or the Range 1..2 as an argument on ‹m›. If a method must be invoked without any arguments you can use ‹without_arguments›: expect mock.to.receive.a(without_arguments) do |m| m.a end You can of course use both ‹Object› and actual arguments: expect mock.to.receive.a(Object, 2, Object) do |m| m.a nil, 2, '3' end The body of the expected method may be given as the block. Here, calling ‹#a› on ‹m› will give the result ‹1›: expect mock.to.receive.a{ 1 } do |m| raise 'not 1' unless m.a == 1 end If no body has been given, the result will be a stub object. To take a block, grab a block parameter and ‹#call› it: expect mock.to.receive.a{ |&b| b.call(1) } do |m| j = 0 m.a{ |i| j = i } raise 'not 1' unless j == 1 end To simulate an ‹#each›-like method, ‹#call› the block several times. Invocation count expectations can be set if the default expectation of “at least once” isn’t good enough. The following expectations are possible • ‹#at_most_once› • ‹#once› • ‹#at_least_once› • ‹#twice› And, for a given ‹N›, • ‹#at_most(N)› • ‹#exactly(N)› • ‹#at_least(N)› § Utilities: Stubs Method stubs are another useful thing to have in a unit testing framework. Sometimes you need to override a method that does something a test shouldn’t do, like access and alter bank accounts. We can override – stub out – a method by using the ‹#stub› method. Let’s assume that we have an ‹Account› class that has two methods, ‹#slips› and ‹#total›. ‹#Slips› retrieves the bank slips that keep track of your deposits to the ‹Account› from a database. ‹#Total› sums the ‹#slips›. In the following test we want to make sure that ‹#total› does what it should do without accessing the database. We therefore stub out ‹#slips› and make it return something that we can easily control. expect 6 do |m| stub(Class.new{ def slips raise 'database not available' end def total slips.reduce(0){ |m, n| m.to_i + n.to_i } end }.new, :slips => [1, 2, 3]){ |account| account.total } end To make it easy to create objects with a set of stubbed methods there’s also a convenience method: expect 3 do s = stub(:a => 1, :b => 2) s.a + s.b end This short-hand notation can also be used for the expected value: expect stub(:a => 1, :b => 2).to.receive.a do |o| o.a + o.b end and also works for mock objects: expect mock(:a => 2, :b => 2).to.receive.a do |o| o.a + o.b end Blocks are also allowed when defining stub methods: expect 3 do s = stub(:a => proc{ |a, b| a + b }) s.a(1, 2) end If need be, we can stub out a specific method on an object: expect 'def' do stub('abc', :to_str => 'def'){ |a| a.to_str } end The stub is active during the execution of the block. § Overriding Constants Sometimes you need to override the value of a constant during the execution of some code. Use ‹#with_const› to do just that: expect 'hello' do with_const 'A::B::C', 'hello' do A::B::C end end Here, the constant ‹A::B::C› is set to ‹'hello'› during the execution of the block. None of the constants ‹A›, ‹B›, and ‹C› need to exist for this to work. If a constant doesn’t exist it’s created and set to a new, empty, ‹Module›. The value of ‹A::B::C›, if any, is restored after the block returns and any constants that didn’t previously exist are removed. § Overriding Environment Variables Another thing you often need to control in your tests is the value of environment variables. Depending on such global values is, of course, not a good practice, but is often unavoidable when working with external libraries. ‹#With_env› allows you to override the value of environment variables during the execution of a block by giving it a ‹Hash› of key/value pairs where the key is the name of the environment variable and the value is the value that it should have during the execution of that block: expect 'hello' do with_env 'INTRO' => 'hello' do ENV['INTRO'] end end Any overridden values are restored and any keys that weren’t previously a part of the environment are removed when the block returns. § Overriding Globals You may also want to override the value of a global temporarily: expect 'hello' do with_global :$stdout, StringIO.new do print 'hello' $stdout.string end end You thus provide the name of the global and a value that it should take during the execution of a block of code. The block gets passed the overridden value, should you need it: expect true do with_global :$stdout, StringIO.new do |overridden| $stdout != overridden end end § Integration Lookout can be used from Rake¹. Simply install Lookout-Rake²: % gem install lookout-rake and add the following code to your Rakefile require 'lookout-rake-3.0' Lookout::Rake::Tasks::Test.new Make sure to read up on using Lookout-Rake for further benefits and customization. ¹ Read more about Rake at http://rake.rubyforge.org/ ² Get information on Lookout-Rake at http://disu.se/software/lookout-rake/ § API Lookout comes with an API¹ that let’s you create things such as new expected values, difference reports for your types, and so on. ¹ See http://disu.se/software/lookout/api/ § Interface Design The default output of Lookout can Spartanly be described as Spartan. If no errors or failures occur, no output is generated. This is unconventional, as unit testing frameworks tend to dump a lot of information on the user, concerning things such as progress, test count summaries, and flamboyantly colored text telling you that your tests passed. None of this output is needed. Your tests should run fast enough to not require progress reports. The lack of output provides you with the same amount of information as reporting success. Test count summaries are only useful if you’re worried that your tests aren’t being run, but if you worry about that, then providing such output doesn’t really help. Testing your tests requires something beyond reporting some arbitrary count that you would have to verify by hand anyway. When errors or failures do occur, however, the relevant information is output in a format that can easily be parsed by an ‹'errorformat'› for Vim or with {Compilation Mode}¹ for Emacs². Diffs are generated for Strings, Arrays, Hashes, and I/O. ¹ Read up on Compilation mode for Emacs at http://www.emacswiki.org/emacs/CompilationMode ² Visit The GNU Foundation’s Emacs’ software page at http://www.gnu.org/software/emacs/ § External Design Let’s now look at some of the points made in the introduction in greater detail. Lookout only allows you to set one expectation per test. If you’re testing behavior with a reception expectation, then only one method-invocation expectation can be set. If you’re testing state, then only one result can be verified. It may seem like this would cause unnecessary duplication between tests. While this is certainly a possibility, when you actually begin to try to avoid such duplication you find that you often do so by improving your interfaces. This kind of restriction tends to encourage the use of value objects, which are easy to test, and more focused objects, which require simpler tests, as they have less behavior to test, per method. By keeping your interfaces focused you’re also keeping your tests focused. Keeping your tests focused improves, in itself, test isolation, but let’s look at something that hinders it: setup and tear-down methods. Most unit testing frameworks encourage test fragmentation by providing setup and tear-down methods. Setup methods create objects and, perhaps, just their behavior for a set of tests. This means that you have to look in two places to figure out what’s being done in a test. This may work fine for few methods with simple set-ups, but makes things complicated when the number of tests increases and the set-up is complex. Often, each test further adjusts the previously set-up object before performing any verifications, further complicating the process of figuring out what state an object has in a given test. Tear-down methods clean up after tests, perhaps by removing records from a database or deleting files from the file-system. The duplication that setup methods and tear-down methods hope to remove is better avoided by improving your interfaces. This can be done by providing better set-up methods for your objects and using idioms such as {Resource Acquisition Is Initialization}¹ for guaranteed clean-up, test or no test. By not using setup and tear-down methods we keep everything pertinent to a test in the test itself, thus improving test isolation. (You also won’t {slow down your tests}² by keeping unnecessary state.) Most unit test frameworks also allow you to create arbitrary test helper methods. Lookout doesn’t. The same rationale as that that has been crystallized in the preceding paragraphs applies. If you need helpers you’re interface isn’t good enough. It really is as simple as that. To clarify: there’s nothing inherently wrong with test helper methods, but they should be general enough that they reside in their own library. The support for mocks in Lookout is provided through a set of test helper methods that make it easier to create mocks than it would have been without them. Lookout-rack³ is another example of a library providing test helper methods (well, one method, actually) that are very useful in testing web applications that use Rack⁴. A final point at which some unit test frameworks try to fragment tests further is documentation. These frameworks provide ways of describing the whats and hows of what’s being tested, the rationale being that this will provide documentation of both the test and the code being tested. Describing how a stack data structure is meant to work is a common example. A stack is, however, a rather simple data structure, so such a description provides little, if any, additional information that can’t be extracted from the implementation and its tests themselves. The implementation and its tests is, in fact, its own best documentation. Taking the points made in the previous paragraphs into account, we should already have simple, self-describing, interfaces that have easily understood tests associated with them. Rationales for the use of a given data structure or system-design design documentation is better suited in separate documentation focused at describing exactly those issues. ¹ Read the Wikipedia entry for Resource Acquisition Is Initialization at http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization ² Read how 37signals had problems with slow Test::Unit tests at http://37signals.com/svn/posts/2742-the-road-to-faster-tests/ ³ Visit the Lookout-rack home page at http://disu.se/software/lookout-rack/ ⁴ Visit the Rack Rubyforge project page at http://rack.rubyforge.org/ § Internal Design The internal design of Lookout has had a couple of goals. • As few external dependencies as possible • As few internal dependencies as possible • Internal extensibility provides external extensibility • As fast load times as possible • As high a ratio of value objects to mutable objects as possible • Each object must have a simple, obvious name • Use mix-ins, not inheritance for shared behavior • As few responsibilities per object as possible • Optimizing for speed can only be done when you have all the facts § External Dependencies Lookout used to depend on Mocha for mocks and stubs. While benchmarking I noticed that a method in Mocha was taking up more than 300 percent of the runtime. It turned out that Mocha’s method for cleaning up back-traces generated when a mock failed was doing something incredibly stupid: backtrace.reject{ |l| Regexp.new(@lib).match(File.expand_path(l)) } Here ‹@lib› is a ‹String› containing the path to the lib sub-directory in the Mocha installation directory. I reported it, provided a patch five days later, then waited. Nothing happened. {254 days later}¹, according to {Wolfram Alpha}², half of my patch was, apparently – I say “apparently”, as I received no notification – applied. By that time I had replaced the whole mocking-and-stubbing subsystem and dropped the dependency. Many Ruby developers claim that Ruby and its gems are too fast-moving for normal package-managing systems to keep up. This is testament to the fact that this isn’t the case and that the real problem is instead related to sloppy practices. Please note that I don’t want to single out the Mocha library nor its developers. I only want to provide an example where relying on external dependencies can be “considered harmful”. ¹ See the Wolfram Alpha calculation at http://www.wolframalpha.com/input/?i=days+between+march+17%2C+2010+and+november+26%2C+2010 ² Check out the Wolfram Alpha computational knowledge engine at http://www.wolframalpha.com/ § Internal Dependencies Lookout has been designed so as to keep each subsystem independent of any other. The diff subsystem is, for example, completely decoupled from any other part of the system as a whole and could be moved into its own library at a time where that would be of interest to anyone. What’s perhaps more interesting is that the diff subsystem is itself very modular. The data passes through a set of filters that depends on what kind of diff has been requested, each filter yielding modified data as it receives it. If you want to read some rather functional Ruby I can highly recommend looking at the code in the ‹lib/lookout/diff› directory. This lookout on the design of the library also makes it easy to extend Lookout. Lookout-rack was, for example, written in about four hours and about 5 of those 240 minutes were spent on setting up the interface between the two. § Optimizing For Speed The following paragraph is perhaps a bit personal, but might be interesting nonetheless. I’ve always worried about speed. The original Expectations library used ‹extend› a lot to add new behavior to objects. Expectations, for example, used to hold the result of their execution (what we now term “evaluation”) by being extended by a module representing success, failure, or error. For the longest time I used this same method, worrying about the increased performance cost that creating new objects for results would incur. I finally came to a point where I felt that the code was so simple and clean that rewriting this part of the code for a benchmark wouldn’t take more than perhaps ten minutes. Well, ten minutes later I had my results and they confirmed that creating new objects wasn’t harming performance. I was very pleased. § Naming I hate low lines (underscores). I try to avoid them in method names and I always avoid them in file names. Since the current “best practice” in the Ruby community is to put ‹BeginEndStorage› in a file called ‹begin_end_storage.rb›, I only name constants using a single noun. This has had the added benefit that classes seem to have acquired less behavior, as using a single noun doesn’t allow you to tack on additional behavior without questioning if it’s really appropriate to do so, given the rather limited range of interpretation for that noun. It also seems to encourage the creation of value objects, as something named ‹Range› feels a lot more like a value than ‹BeginEndStorage›. (To reach object-oriented-programming Nirvana you must achieve complete value.) § News § 3.0.0 The ‹xml› expectation has been dropped. It wasn’t documented, didn’t suit very many use cases, and can be better implemented by an external library. The ‹arg› argument matcher for mock method arguments has been removed, as it didn’t provide any benefit over using Object. The ‹#yield› and ‹#each› methods on stub and mock methods have been removed. They were slightly weird and their use case can be implemented using block parameters instead. The ‹stub› method inside ‹expect› blocks now stubs out the methods during the execution of a provided block instead of during the execution of the whole except block. When a mock method is called too many times, this is reported immediately, with a full backtrace. This makes it easier to pin down what’s wrong with the code. Query expectations were added. Explicit query expectations were added. Fluent boolean expectations, for example, ‹expect nil.to.be.nil?› have been replaced by query expectations (‹expect :nil? do nil end›) and explicit query expectations (‹expect result.to.be.nil? do nil end›). This was done to discourage creating objects as the expected value and creating objects that change during the course of the test. The ‹literal› expectation was added. Equality (‹#==›) is now checked before “caseity” (‹#===›) for modules, ranges, and regular expressions to match the documentation. § Financing Currently, most of my time is spent at my day job and in my rather busy private life. Please motivate me to spend time on this piece of software by donating some of your money to this project. Yeah, I realize that requesting money to develop software is a bit, well, capitalistic of me. But please realize that I live in a capitalistic society and I need money to have other people give me the things that I need to continue living under the rules of said society. So, if you feel that this piece of software has helped you out enough to warrant a reward, please PayPal a donation to now@disu.se¹. Thanks! Your support won’t go unnoticed! ¹ Send a donation: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=now%40disu%2ese&item_name=Lookout § Reporting Bugs Please report any bugs that you encounter to the {issue tracker}¹. ¹ See https://github.com/now/lookout/issues § Contributors Contributors to the original expectations codebase are mentioned there. We hope no one on that list feels left out of this list. Please {let us know}¹ if you do. • Nikolai Weibull ¹ Add an issue to the Lookout issue tracker at https://github.com/now/lookout/issues § Licensing Lookout is free software: you may redistribute it and/or modify it under the terms of the {GNU Lesser General Public License, version 3}¹ or later², as published by the {Free Software Foundation}³. ¹ See http://disu.se/licenses/lgpl-3.0/ ² See http://gnu.org/licenses/ ³ See http://fsf.org/