From 82ec4ff5476a403e01bcb4548f2f9be92fc9f307 Mon Sep 17 00:00:00 2001 From: kpurdon Date: Thu, 18 Jun 2015 17:31:15 -0600 Subject: [PATCH 1/3] initial commit --- golang/README.md | 25 ++++++++++++++++ golang/gomr.go | 60 ++++++++++++++++++++++++++++++++++++++ golang/register.go | 50 ++++++++++++++++++++++++++++++++ golang/run.go | 57 ++++++++++++++++++++++++++++++++++++ python/README.md | 31 ++++++++++++++++++++ python/pymr/__init__.py | 0 python/pymr/__init__.pyc | Bin 0 -> 132 bytes python/pymr/pymr.py | 61 +++++++++++++++++++++++++++++++++++++++ python/pymr/pymr.pyc | Bin 0 -> 1954 bytes python/setup.py | 37 ++++++++++++++++++++++++ ruby/Gemfile | 3 ++ ruby/README.md | 29 +++++++++++++++++++ ruby/bin/rumr | 5 ++++ ruby/lib/rumr.rb | 54 ++++++++++++++++++++++++++++++++++ ruby/rumr.gemspec | 14 +++++++++ 15 files changed, 426 insertions(+) create mode 100644 golang/README.md create mode 100644 golang/gomr.go create mode 100644 golang/register.go create mode 100644 golang/run.go create mode 100644 python/README.md create mode 100644 python/pymr/__init__.py create mode 100644 python/pymr/__init__.pyc create mode 100644 python/pymr/pymr.py create mode 100644 python/pymr/pymr.pyc create mode 100644 python/setup.py create mode 100644 ruby/Gemfile create mode 100644 ruby/README.md create mode 100755 ruby/bin/rumr create mode 100755 ruby/lib/rumr.rb create mode 100644 ruby/rumr.gemspec diff --git a/golang/README.md b/golang/README.md new file mode 100644 index 0000000..ce1fb24 --- /dev/null +++ b/golang/README.md @@ -0,0 +1,25 @@ +gomr: go [m]utli-[r]epository manager +----- + +gomr is a golang command-line tool used to execute arbitrary commands on a set of tagged directories. + +## Features + +* Configuration is stored in the registered directory (allows for version-controlled configuration) +* Any arbitrary command can be run, no ties to version-control specific commands. + + +## Installation + +I'm not distributing binaries yet (TODO) for now checkout and `go install` to build the binary. + +## Command Help + +For help run: +* `gomr --help` +* `gomr register --help` +* `gomr run --help` + +## Ports + +Pymr has been ported to ruby as [rumr](https://github.com/kpurdon/rumr) and python as [pymr](https://github.com/kpurdon/pymr). diff --git a/golang/gomr.go b/golang/gomr.go new file mode 100644 index 0000000..003f91f --- /dev/null +++ b/golang/gomr.go @@ -0,0 +1,60 @@ +package main + +import ( + "github.com/codegangsta/cli" + "os" +) + +func main() { + app := cli.NewApp() + app.Name = "gomr" + app.Author = "Kyle W. Purdon" + app.Version = "0.0.1" + app.Usage = "multi-repository manager in go" + app.Commands = []cli.Command{ + { + Name: "register", + Usage: "register a directory", + Action: register, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "directory, d", + Value: "./", + Usage: "directory to tag", + }, + cli.StringFlag{ + Name: "tag, t", + Value: "default", + Usage: "tag to add for directory", + }, + cli.BoolFlag{ + Name: "append", + Usage: "append the tag to an existing registered directory", + }, + }, + }, + { + Name: "run", + Usage: "run a command", + Action: run, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "basepath, b", + Value: "./", + Usage: "path to begin the recursive search", + }, + cli.StringFlag{ + Name: "tag, t", + Value: "default", + Usage: "tag to add for directory", + }, + cli.BoolFlag{ + Name: "dryrun, d", + Usage: "print (dont execute) the commands that will be run", + }, + }, + }, + } + + app.Run(os.Args) +} diff --git a/golang/register.go b/golang/register.go new file mode 100644 index 0000000..221f305 --- /dev/null +++ b/golang/register.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "github.com/codegangsta/cli" + "github.com/robfig/config" + "os" + "path" + "strings" +) + +func AppendIfMissing(slice []string, i string) []string { + for _, ele := range slice { + if ele == i { + return slice + } + } + return append(slice, i) +} + +func register(ctx *cli.Context) { + + fn := path.Join(ctx.String("directory"), ".gomr") + + newTags := strings.Split(ctx.String("tag"), ",") + + if ctx.Bool("append") { + if _, err := os.Stat(fn); err == nil { + cfg, _ := config.ReadDefault(".gomr") + curTags, _ := cfg.String("gomr", "tags") + + for _, tag := range strings.Split(curTags, ",") { + newTags = AppendIfMissing(newTags, tag) + } + } else { + err := "append used, existing file not found." + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } + } + + outTags := strings.Join(newTags, ",") + + outCfg := config.NewDefault() + outCfg.AddSection("gomr") + outCfg.AddOption("gomr", "tags", outTags) + outCfg.WriteFile(fn, 0644, "gomr configuration file") + + return +} diff --git a/golang/run.go b/golang/run.go new file mode 100644 index 0000000..263ecfe --- /dev/null +++ b/golang/run.go @@ -0,0 +1,57 @@ +package main + +import ( + "fmt" + "github.com/codegangsta/cli" + "github.com/robfig/config" + "os" + "os/exec" + "path/filepath" + "strings" +) + +func RunGomr(ctx *cli.Context) filepath.WalkFunc { + return func(path string, f os.FileInfo, err error) error { + + tag := ctx.String("tag") + dryrun := ctx.Bool("dryrun") + + if len(ctx.Args()) == 0 { + panic("no command given") + } + + command := ctx.Args()[0] + + if strings.Contains(path, ".gomr") { + + cfg, _ := config.ReadDefault(path) + gomrTags, _ := cfg.String("gomr", "tags") + + if strings.Contains(gomrTags, tag) { + if dryrun { + fmt.Printf("Would run %s in %s\n", command, filepath.Dir(path)) + } else { + os.Chdir(filepath.Dir(path)) + cmd := exec.Command("sh", "-c", command) + stdout, err := cmd.Output() + + if err != nil { + panic(err.Error()) + } + + println(string(stdout)) + } + } + + } + return nil + } +} + +func run(ctx *cli.Context) { + + root := ctx.String("basepath") + filepath.Walk(root, RunGomr(ctx)) + + return +} diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..ff8441e --- /dev/null +++ b/python/README.md @@ -0,0 +1,31 @@ +pymr: [Py]thon [m]utli-[r]epository Tool +----- + +PyMR is a python command-line tool used to execute arbitrary commands on a set of tagged directories. + +## Features + +* Configuration is stored in the registered directory (allows for version-controlled configuration) +* Any arbitrary command can be run, no ties to version-control specific commands. + +## Where is PyMR + +PyMR source is available on [Github](https://github.com/kpurdon/pymr) and is released to [PyPI](https://pypi.python.org/pypi/pymr). + +## Installation + +From pypi: `pip install pymr` + +From source: +* `python setup.py install` + +## Command Help + +For help run: +* `pymr --help` +* `pymr register --help` +* `pymr run --help` + +## Ports + +Pymr has been ported to ruby as [rumr](https://github.com/kpurdon/rumr) and go as [gomr](https://github.com/kpurdon/gomr). diff --git a/python/pymr/__init__.py b/python/pymr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/pymr/__init__.pyc b/python/pymr/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd94b2f8b09f080d5e28f5ff62192c3d10fc5bea GIT binary patch literal 132 zcmZSn%*%C+H8V7s0SXv_v;zZMb5Un2Dlh|=S!HHIUS#Ver$!m}}fP@edNN|e4VYSE-vc}VGXPo&;cMrSK z#s?Ixh<^b${v0UB5%-0l1oUp|g${cPj^bA0Y&MWpd6 zN{J?SeMJ+s!2_BE(hn$YP}ZbLlhk@mizcm&T}YFVOi0Tux`m?#{Y-QVN84n!Y1w9w zXFH^uq_HX_4a-2S!EMrQ(j788_rM*}T|t!6yK9H=S-%rZ-S@mpdXJ~p4);kP{P!-m z!N%tBp8unl{d4epWZ>sDx(vvEq_7>bP`MCxK6o7rCV%5O8-PagO*CMS0`>dPoqWL zz(k0TmrGhdq2q~`ik1Q02rJ-r)u03g(xhdJe7Hsct}xspgmHsa zEjj_jXp^oqfo)#U=DB)}4j>VJ6;yUUU~=8tXgYm7)+`d|jqOMMJ1S&CvIcjV^-S2) z+1Y^ElR@EXMk9YAon?{a*b9n%24_yXWfEVeMyAsu($Xowk!L!51?D^xDr3eTZjk9Tk1~TO0K9t2?E6!w lpi0eW9ERg=4,<5', + 'pathlib>=1,<2' + ], + 'entry_points': { + 'console_scripts': [ + 'pymr = pymr.pymr:pymr', + ] + } +} + + +setup( + name='pymr', + description='A tool for executing ANY command in a set of tagged directories.', + author='Kyle W Purdon', + author_email='kylepurdon@gmail.com', + url='https://github.com/kpurdon/pymr', + download_url='https://github.com/kpurdon/pymr', + version='2.0.1', + packages=find_packages(), + classifiers=classifiers, + **setuptools_kwargs +) diff --git a/ruby/Gemfile b/ruby/Gemfile new file mode 100644 index 0000000..98c294d --- /dev/null +++ b/ruby/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'thor', '~>0.19.1' diff --git a/ruby/README.md b/ruby/README.md new file mode 100644 index 0000000..64543f7 --- /dev/null +++ b/ruby/README.md @@ -0,0 +1,29 @@ +rumr: [Ru]by [m]utli-[r]epository Tool +----- + +Rumr is a ruby command-line tool used to execute arbitrary commands on a set of tagged directories. + +## Features + +* Configuration is stored in the registered directory (allows for version-controlled configuration) +* Any arbitrary command can be run, no ties to version-control specific commands. + + +## Installation + +From rubygems: `gem install rumr` + +From source: +* `gem build rumr.gemspec` +* `gem install rumr-[version].gem` + +## Command Help + +For help run: +* `rumr help` +* `rumr help register` +* `rumr help exec` + +## Ports + +rumr has been ported to python as [pymr](https://github.com/kpurdon/pymr) and go as [gomr](https://github.com/kpurdon/gomr). diff --git a/ruby/bin/rumr b/ruby/bin/rumr new file mode 100755 index 0000000..29e84ec --- /dev/null +++ b/ruby/bin/rumr @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby + +require 'rumr' + +Rumr.start(ARGV) diff --git a/ruby/lib/rumr.rb b/ruby/lib/rumr.rb new file mode 100755 index 0000000..8fd5bf2 --- /dev/null +++ b/ruby/lib/rumr.rb @@ -0,0 +1,54 @@ +require 'find' +require 'yaml' +require 'thor' + +# [Ru]by [m]ulti-[r]epository command line tool +class Rumr < Thor + desc 'register', 'Register a directory' + method_option :directory, + aliases: '-d', + type: :string, + default: './', + desc: 'Directory to register' + method_option :tag, + aliases: '-t', + type: :array, + default: 'default', + desc: 'Tag/s to register' + method_option :append, + type: :boolean, + desc: 'Append given tags to any existing tags?' + def register + new_tags = options[:tag] + rumr_file = File.join(options[:directory], '.rumr') + if options[:append] && File.exist?(rumr_file) + new_tags |= YAML.load_file(rumr_file) + end + IO.write(rumr_file, new_tags.to_yaml) + end + + desc 'exec COMMAND', 'Execute (run) a given command on registered directories' + method_option :basepath, + aliases: '-b', + type: :string, + default: './', + desc: 'Directory to begin search for rumr files.' + method_option :tag, + aliases: '-t', + type: :string, + default: 'default', + desc: 'Tag to match against' + method_option :dryrun, + type: :boolean, + desc: 'Display (do not execute) the commands to run.' + def exec(command) + Dir[File.join(options[:basepath], '**/.rumr')].each do |path| + next unless YAML.load_file(path).include? options[:tag] + if options['dryrun'] + puts "Would execute #{command} in #{path}" + else + Dir.chdir(File.dirname(path)) { puts `#{command}` } + end + end + end +end diff --git a/ruby/rumr.gemspec b/ruby/rumr.gemspec new file mode 100644 index 0000000..7ac7bd9 --- /dev/null +++ b/ruby/rumr.gemspec @@ -0,0 +1,14 @@ +Gem::Specification.new do |s| + s.name = 'rumr' + s.version = '1.0.2' + s.summary = 'Run system commands in sets' \ + ' of registered and tagged directories.' + s.description = '[Ru]by [m]ulti-[r]epository Tool' + s.authors = ['Kyle W. Purdon'] + s.email = 'kylepurdon@gmail.com' + s.files = ['lib/rumr.rb'] + s.homepage = 'https://github.com/kpurdon/rumr' + s.license = 'GPLv3' + s.executables << 'rumr' + s.add_dependency('thor', ['~>0.19.1']) +end From 5c6090cb3fc45157bc82c2edd5f16a5c361e34af Mon Sep 17 00:00:00 2001 From: kpurdon Date: Thu, 18 Jun 2015 17:33:43 -0600 Subject: [PATCH 2/3] Update README.md's --- README.md | 12 ++++++++++++ golang/README.md | 4 ---- python/README.md | 4 ---- ruby/README.md | 4 ---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index e69de29..3e6aff0 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,12 @@ +python-ruby-golang +---------- + +A comparison of click (Python), thor (Ruby), and cli.go (Golang) for building a very simple command-line tool. + +## QuickStart + +See the README.md in each of the subdirectories for more information. + +## Blog Post + +[Coming Soon!]() diff --git a/golang/README.md b/golang/README.md index ce1fb24..faff740 100644 --- a/golang/README.md +++ b/golang/README.md @@ -19,7 +19,3 @@ For help run: * `gomr --help` * `gomr register --help` * `gomr run --help` - -## Ports - -Pymr has been ported to ruby as [rumr](https://github.com/kpurdon/rumr) and python as [pymr](https://github.com/kpurdon/pymr). diff --git a/python/README.md b/python/README.md index ff8441e..9453d90 100644 --- a/python/README.md +++ b/python/README.md @@ -25,7 +25,3 @@ For help run: * `pymr --help` * `pymr register --help` * `pymr run --help` - -## Ports - -Pymr has been ported to ruby as [rumr](https://github.com/kpurdon/rumr) and go as [gomr](https://github.com/kpurdon/gomr). diff --git a/ruby/README.md b/ruby/README.md index 64543f7..0dbe795 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -23,7 +23,3 @@ For help run: * `rumr help` * `rumr help register` * `rumr help exec` - -## Ports - -rumr has been ported to python as [pymr](https://github.com/kpurdon/pymr) and go as [gomr](https://github.com/kpurdon/gomr). From a644e08ac3cf17cf4b417b31ac184c5f159be891 Mon Sep 17 00:00:00 2001 From: kpurdon Date: Thu, 18 Jun 2015 17:34:58 -0600 Subject: [PATCH 3/3] Add blog post --- post.md | 465 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 post.md diff --git a/post.md b/post.md new file mode 100644 index 0000000..2585712 --- /dev/null +++ b/post.md @@ -0,0 +1,465 @@ +--- +layout: post +title: "Python, Ruby, and Golang: A Command-Line Application Comparison" +author: Kyle W. Purdon +categories: [python, ruby, golang, development] +--- + +**Edit (5/31):** There has been lots of great discussion on my [reddit post](http://www.reddit.com/r/programming/comments/37x1o0/python_ruby_and_golang_a_commandline_application/). I have made some edits (with notes) on many of the code examples you will see throughout this post. + +----- + +In late 2014 I built a tool called [pymr](https://github.com/kpurdon/pymr). I recently felt the need to learn golang and refresh my ruby knowledge so I decided to revisit the idea of pymr and build it in multiple languages. In this post I will break down the "mr" (merr) application (pymr, gomr, rumr) and present the implementation of specific pieces in each language. I will provide an overall personal preference at the end but will leave the comparison of individual pieces up to you. + +For those that want to skip directly to the code here are the repos: + +* [Python - pymr](https://github.com/kpurdon/pymr) +* [Ruby - rumr](https://github.com/kpurdon/rumr) +* [Golang - gomr](https://github.com/kpurdon/gomr) + + + +Application Structure +===== + +The basic idea of this application is that you have some set of related directories that you want to execute a single command on. The "mr" tool provides a method for registering directories, and a method for running commands on groups of registered directories. The application has the following components: + +* A command-line interface +* A registration command (writes a file with given tags) +* A run command (runs a given command on registered directories) + +Command-Line Interface +========== +----- + +The command line interface for the "mr" tools is: + +{% highlight bash %} +$ pymr --help +Usage: pymr [OPTIONS] COMMAND [ARGS]... + +Options: + --help Show this message and exit. + +Commands: + register register a directory + run run a given command in matching... +{% endhighlight %} + +To compare building the command-line interface let's take a look at the register command in each language. + +#### Python (pymr) + +To build the command line interface in python I chose to use the [click](http://click.pocoo.org/4/) package. + +{% highlight python %} +@pymr.command() +@click.option('--directory', '-d', default='./') +@click.option('--tag', '-t', multiple=True) +@click.option('--append', is_flag=True) +def register(directory, tag, append): + ... +{% endhighlight %} + +#### Ruby (rumr) + +To build the command line interface in ruby I chose to use the [thor](http://whatisthor.com/) gem. + +{% highlight ruby %} +desc 'register', 'Register a directory' +method_option :directory, + aliases: '-d', + type: :string, + default: './', + desc: 'Directory to register' +method_option :tag, + aliases: '-t', + type: :array, + default: 'default', + desc: 'Tag/s to register' +method_option :append, + type: :boolean, + desc: 'Append given tags to any existing tags?' +def register + ... +{% endhighlight %} + +#### Golang (gomr) + +To build the command line interface in Golang I chose to use the [cli.go](https://github.com/codegangsta/cli) package. + +{% highlight go %} +app.Commands = []cli.Command{ + { + Name: "register", + Usage: "register a directory", + Action: register, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "directory, d", + Value: "./", + Usage: "directory to tag", + }, + cli.StringFlag{ + Name: "tag, t", + Value: "default", + Usage: "tag to add for directory", + }, + cli.BoolFlag{ + Name: "append", + Usage: "append the tag to an existing registered directory", + }, + }, + }, +{% endhighlight %} + + +Registration +========== +----- + +The registration logic is as follows: + +1. If the user asks to `--append` read the `.[py|ru|go]mr` file if it exits. +2. Merge the existing tags with the given tags. +3. Write a new `.[...]mr` file with the new tags. + +This breaks down into a few small tasks we can compare in each language: + +* Searching for and reading a file. +* Merging two items (only keeping the unique set) +* Writing a file + +### File Search + +#### Python (pymr) + +For python this invloves the [os](https://docs.python.org/2/library/os.html) module. + +{% highlight python %} +pymr_file = os.path.join(directory, '.pymr') +if os.path.exists(pymr_file): + ... +{% endhighlight %} + +#### Ruby (rumr) + +For ruby this involves the [File](http://ruby-doc.org/core-1.9.3/File.html) class. + +{% highlight ruby %} +rumr_file = File.join(directory, '.rumr') +if File.exist?(rumr_file) + ... +{% endhighlight %} + + +#### Golang (gomr) + +For golang this involves the [path](http://golang.org/pkg/path/) package. + +{% highlight go %} +fn := path.Join(directory, ".gomr") +if _, err := os.Stat(fn); err == nil { + ... +{% endhighlight %} + + +### Unique Merge + +#### Python (pymr) + +For python this involves the use of a [set](https://docs.python.org/2/library/sets.html). + +{% highlight python %} +# new_tags and cur_tags are tuples +new_tags = tuple(set(new_tags + cur_tags)) +{% endhighlight %} + +#### Ruby (rumr) + +For ruby this involves the use of the [.uniq](http://ruby-doc.org/core-2.2.0/Array.html#method-i-uniq) array method. + +{% highlight ruby %} +# Edited (5/31) +# old method: +# new_tags = (new_tags + cur_tags).uniq + +# new_tags and cur_tags are arrays +new_tags |= cur_tags +{% endhighlight %} + +#### Golang (gomr) + +For golang this involves the use of custom function. + +{% highlight go %} +func AppendIfMissing(slice []string, i string) []string { + for _, ele := range slice { + if ele == i { + return slice + } + } + return append(slice, i) +} + +for _, tag := range strings.Split(curTags, ",") { + newTags = AppendIfMissing(newTags, tag) + } +{% endhighlight %} + + +### File Read/Write + +I tried to choose the simplest possible file format to use in each language. + +#### Python (pymr) + +For python this involves the use of the [pickle](https://docs.python.org/2/library/pickle.html) module. + +{% highlight python %} +# read +cur_tags = pickle.load(open(pymr_file)) + +# write +pickle.dump(new_tags, open(pymr_file, 'wb')) +{% endhighlight %} + +#### Ruby (rumr) + +For ruby this involves the use of the [YAML](http://ruby-doc.org/stdlib-2.2.1/libdoc/yaml/rdoc/YAML.html) module. + +{% highlight ruby %} +# read +cur_tags = YAML.load_file(rumr_file) + +# write +# Edited (5/31) +# old method: +# File.open(rumr_file, 'w') { |f| f.write new_tags.to_yaml } +IO.write(rumr_file, new_tags.to_yaml) +{% endhighlight %} + +#### Golang (gomr) + +For golang this involves the use of the [config](https://github.com/robfig/config) package. + +{% highlight go %} +// read +cfg, _ := config.ReadDefault(".gomr") + +// write +outCfg.WriteFile(fn, 0644, "gomr configuration file") +{% endhighlight %} + + +Run (Command Execution) +========== +----- + +The run logic is as follows: + +1. Recursively walk from the given basepath searching for `.[...]mr` files +2. Load a found file, and see if the given tag is in it +3. Call the given command in the directory of a matching file. + +This breaks down into a few small tasks we can compare in each language: + +* Recursive Directory Search +* String Comparison +* Calling a Shell Command + +### Recursive Directory Search + +#### Python (pymr) + +For python this involves the [os](https://docs.python.org/2/library/os.html) module and [fnmatch](https://docs.python.org/2/library/fnmatch.html) module. + +{% highlight python %} +for root, _, fns in os.walk(basepath): + for fn in fnmatch.filter(fns, '.pymr'): + ... +{% endhighlight %} + +#### Ruby (rumr) + +For ruby this involves the [Find](http://ruby-doc.org/stdlib-2.2.0/libdoc/find/rdoc/Find.html) and [File](http://ruby-doc.org/core-2.2.0/File.html) classes. + +{% highlight ruby %} +# Edited (5/31) +# old method: +# Find.find(basepath) do |path| +# next unless File.basename(path) == '.rumr' +Dir[File.join(options[:basepath], '**/.rumr')].each do |path| + ... +{% endhighlight %} + +#### Golang (gomr) + +For golang this requires the [filepath](http://golang.org/pkg/path/filepath/) package and a custom callback function. + +{% highlight go %} +func RunGomr(ctx *cli.Context) filepath.WalkFunc { + return func(path string, f os.FileInfo, err error) error { + ... + if strings.Contains(path, ".gomr") { + ... + +filepath.Walk(root, RunGomr(ctx)) +{% endhighlight %} + +### String Comparison + +#### Python (pymr) + +Nothing additional is needed in python for this task. + +{% highlight python %} +if tag in cur_tags: + ... +{% endhighlight %} + +#### Ruby (rumr) + +Nothing additional is needed in ruby for this task. + +{% highlight ruby %} +if cur_tags.include? tag + ... +{% endhighlight %} + +#### Golang (gomr) + +For golang this requires the [strings](http://golang.org/pkg/strings/) package. + +{% highlight go %} +if strings.Contains(cur_tags, tag) { + ... +{% endhighlight %} + +### Calling a Shell Command + +#### Python (pymr) + +For python this requires the [os](https://docs.python.org/2/library/os.html) module and the [subprocess](https://docs.python.org/2/library/subprocess.html) module. + +{% highlight python %} +os.chdir(root) +subprocess.call(command, shell=True) +{% endhighlight %} + +#### Ruby (rumr) + +For ruby this involves the [Kernel](http://ruby-doc.org/core-2.2.0/Kernel.html#method-i-system) module and the Backticks syntax. + +{% highlight ruby %} +# Edited (5/31) +# old method +# puts `bash -c "cd #{base_path} && #{command}"` +Dir.chdir(File.dirname(path)) { puts `#{command}` } +{% endhighlight %} + +#### Golang (gomr) + +For golang this involves the [os](https://golang.org/pkg/os/) package and the [os/exec](https://golang.org/pkg/os/exec/) package. + +{% highlight go %} +os.Chdir(filepath.Dir(path)) +cmd := exec.Command("bash", "-c", command) +stdout, err := cmd.Output() +{% endhighlight %} + +Packaging +========== +----- + +The ideal mode of distribution for this tool is via a package. A user could then install it `tool install [pymr,rumr,gomr]` and have a new command on there systems path to execute. I dont want to go into packaging systems here, rather I will just show the basic configuration file needed in each language. + +#### Python (pymr) + +For python a `setup.py` is required. Once the package is created and uploaded it can be installed with `pip install pymr`. + +{% highlight python %} +from setuptools import setup, find_packages + +classifiers = [ + 'Environment :: Console', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', + 'Intended Audience :: Developers', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7' +] + +setuptools_kwargs = { + 'install_requires': [ + 'click>=4,<5' + ], + 'entry_points': { + 'console_scripts': [ + 'pymr = pymr.pymr:pymr', + ] + } +} + + +setup( + name='pymr', + description='A tool for executing ANY command in a set of tagged directories.', + author='Kyle W Purdon', + author_email='kylepurdon@gmail.com', + url='https://github.com/kpurdon/pymr', + download_url='https://github.com/kpurdon/pymr', + version='2.0.1', + packages=find_packages(), + classifiers=classifiers, + **setuptools_kwargs +) +{% endhighlight %} + +#### Ruby (rumr) + +For ruby a `rumr.gemspec` is required. Once the gem is created and uploaded is can be installed with `gem install rumr`. + +{% highlight ruby %} +Gem::Specification.new do |s| + s.name = 'rumr' + s.version = '1.0.0' + s.summary = 'Run system commands in sets' \ + ' of registered and tagged directories.' + s.description = '[Ru]by [m]ulti-[r]epository Tool' + s.authors = ['Kyle W. Purdon'] + s.email = 'kylepurdon@gmail.com' + s.files = ['lib/rumr.rb'] + s.homepage = 'https://github.com/kpurdon/rumr' + s.license = 'GPLv3' + s.executables << 'rumr' + s.add_dependency('thor', ['~>0.19.1']) +end +{% endhighlight %} + +#### Golang (gomr) + +For golang the source is simply compiled into a binary that can be redistributed. There is no additional file needed and currently no package repository to push to. + +Conclusion +========== +----- + +For this tool Golang feels like the wrong choice. I dont need it to be very performant and I'm not utilizing the native concurrency Golang has to offer. This leaves me with Ruby and Python. For about 80% of the logic my personal preference is a toss-up between the two. Here are the pieces I find better in one language: + +### Command-Line Interface Declaration + +Python is the winner here. The [click]() libraries decorator style declaration is clean and simple. Keep in mind I have only tried the Ruby [thor]() gem so there may be better solutions in Ruby. This is also not a commentary on either language, rather that the CLI library I used in python is my preference. + +### Recursive Directory Search + +Ruby is the winner here. I found that this entire section of code was much cleaner and more readable using ruby's `Find.find()` and especially the `next unless` syntax. + +### Packaging + +Ruby is the winner here. The `rumr.gemspec` file is much simpler and the process of building and pushing a gem was much simpler as well. The [bundler](http://bundler.io/) tool also makes installing in semi-isolated environments a snap. + +## Final Determination + +Because of packaging and the recursive directory search preference I would choose Ruby as the tool for this application. However the differences in preference were so minor that Python would be more than fitting as well. Golang however, is not the correct tool here.