RSpec - script to upgrade from 'should' to 'expect' syntax? - ruby

I have hundreds of files that also have hundreds of 'should' statements.
Is there any sort of automated way to update these files to the new syntax?
I'd like options to both create new files and also modify the existing files inline.

sed is a good tool for this.
The following will process all the files in the current directory and write them out to new files in a _spec_seded directory. This currently handle about 99%+ of the changes but might still leave you with a couple of manual changes to make (the amount will depend on your code and coding style).
As always with a sed script you should check the results, run diffs and look at the files manually. Ideally you are using git which helps make the diffs even easier.
filenum=1
find . -type f -name '*_spec.rb' | while read file; do
mkdir -p ../_spec_seded/"${file%/*}"
echo "next file...$filenum...$file"
let filenum+=1
cp "$file" ../_spec_seded/"$file"
sed -i ' # Exclude:
/^ *describe .*do/! { # -describe...do descriptions
/^ *it .*do/! { # -it...do descriptions
/^[[:blank:]]*\#/! { # -comments
/^ *def .*\.should.*/! { # -inline methods
/\.should/ {
s/\.should/)\.to/ # Change .should to .to
s/\(\S\)/expect(\1/ # Add expect( at start of line.
/\.to\( \|_not \)>\=/ s/>\=/be >\=/ # Change operators for
/\.to\( \|_not \)>[^=]/ s/>/be >/ # >, >=, <, <= and !=
/\.to\( \|_not \)<\=/ s/<\=/be <\=/
/\.to\( \|_not \)<[^=]/ s/</be </
/\.to\( \|_not \)\!\=/ s/\!\=/be \!\=/
}
/\.to +==\( +\|$\)/ s/==/eq/
/=\~/ { # Change match operator
s/=\~/match(/
s/$/ )/
s/\[ )$/\[/
}
s/[^}.to|end.to]\.to /).to / # Add paren
/eq ({.*} )/ s/ ({/ ( {/ # Add space
/to\(_\|_not_\)receive/ s/_receive/ receive/ # receive
/\.to eq \[.*\]/ {
s/ eq \[/ match_array([/
s/\]$/\])/
}
/expect.*(.*lambda.*{.*})/ { # Remove unneeded lambdas
s/( *lambda *{/{/
s/ })\.to / }\.to /
}
/expect *{ *.*(.*) *})\.to/ { # Fix extra end paren
s/})\.to/}\.to/
}
}
}
}
}' ../_spec_seded/"$file"
done
Please use with caution. Currently the script create new files in _seded/ for review first for safety. The script is placed in /spec directory and run from there.
If you have hundreds of files this could save you hours or days of work!
If you use this I recommend that "step 2" is do manually copy files from _spec_seded to spec itself and run them. I recommend that you don't just rename the whole directories. For one thing, files, such as spec_helper.rb aren't currently copied to _spec_seded.
11/18/2013 Note: I continue to upgrade this script. Covering more edge cases and also making matches more specific and also excluding more edge cases, e.g. comment lines.
P.S. The differences which should be reviewed can be seen with (from the project directory root):
diff -r /spec /_spec_seded
git also has nice diff options but I like to look before adding files to git at all.

Belated update, mainly for those who may find their way to this page via a search engine.
Use Yuji Nakayama's excellent Transpec gem for this purpose. I've used it over 10 times now on different projects without issue.
From the website:
Transpec lets you upgrade your RSpec 2 specs to RSpec 3 in no time. It supports conversions for almost all of the RSpec 3 changes, and it’s recommended by the RSpec team.
Also, you can use it on your RSpec 2 project even if you’re not going to upgrade it to RSpec 3 for now.

Related

Avoid "stack smashing detected" when recursively listing file or directories

I've to rename around 5 000 folders located in a remote storage. Running Dir['**/*/'] returns an error "*** stack smashing detected ***" and invites me to report the bug as it might occurs during the interpretation process (see bug report)
If it can help, here's the script I was planning to run (works fine on a test environment, though it's quite specific to my needs)
#!/usr/bin/env ruby
# Fetch root directories
dirs = Dir['**/*/'].select { |d| d =~ /\d([\.-]{1}\d{2,})?/ }
# Order subdirectories first
dirs = dirs.sort_by { |d| d.count('/') }.reverse
# Substitute "." and "-" placed after the last "/" with "_"
dirs.each do |dir|
File.rename(dir, dir.gsub(/[\.-](?!.*\/.*)/, '_'))
end
Any suggestion for mitigating this issue ?
It's neither a well formed question, nor a generic answer, but I managed to get around the issue by specifying the deepness to look at. Concretely, I replaced Dir['**/*/'] by Dir['*/*/*/*/'].
However I'm open to other suggestions, as others may face similar issues without the possibility to hardcode the deepness to look at.

Parslet grammar for rules starting identical

I want to provide a parser for parsing so called Subversion config auth files (see patch based authorization in the Subversion red book). Here I want to define rules for directories like
[/]
* = r
[/trunk]
#PROJECT = rw
So the part of the grammar I have problems is the path definition. I currently have the following rules in Parslet:
rule(:auth_rule_head) { (str('[') >> path >> str(']') >> newline).as(:arh) }
rule(:top) { (str('/')).as(:top) }
rule(:path) { (top | ((str('/') >> path_ele).repeat)).as(:path) }
rule(:path_ele) { ((str('/').absent? >> any).repeat).as(:path_ele) }
So I want to divide in two cases:
To find only [/] (the root directory)
in all other cases [/<dir>] which may be repeated, but has to end without a /
The problematic rule seems to be the path that defines an alternative, here / XOR something like /trunk
I have defined test cases for those, and get the following error when running the test case:
Failed to match sequence (SPACES '[' PATH ']' NEWLINE) at line 1 char 3.
`- Expected "]", but got "t" at line 1 char 3.
So the problem seems to be, that the alternative (rule :path) is chosen all the time top.
What is a solution (as a grammar) for this problem? I think there should be a solution, and this looks like something idiomatic that should happen from here to there. I am not an expert at all with PEG parsers or parser / compiler generation, so if that is a base problem not solvable, I would like to know that as well.
In short: Swap the OR conditions around.
Parlset rules consume the input stream until they get a match, then they stop.
If you have two possible options (an OR), the first is tried, and only if it doesn't match is the second tried.
In your case, as all your paths start with '/' they all match the first part of the path rule, so the second half is never explored.
You need to try to match the full path first, and only match the 'top' if it fails.
# changing this
rule(:path) { (top | ((str('/') >> path_ele).repeat)).as(:path) }
# to this
rule(:path) { ((str('/') >> path_ele).repeat) | top).as(:path) }
# fixes your first problem :)
Also... Be careful of rules that can consume nothing being in a loop.
Repeat by default is repeat(0). Usually it needs to be repeat (1).
rule(:path) { ((str('/') >> path_ele).repeat(1)) | top).as(:path) }
also...
Is "top" really a special case? All paths end in a "/", so top is just the zero length path.
rule(:path) { (path_ele.repeat(0) >> str('/')).as(:path) }
Or
rule(:path) { (str('/') >> path_ele.repeat(0)).as(:path) }
rule(:path_ele) { ((str('/').absent? >> any).repeat(0)).as(:path_ele) >> str('/') }
# assuming "//" is valid otherwise repeat(1)
Seems to be I have not got the problem right. I have tried to reproduce the problem in creating a small example grammar including some unit tests, but now, the thing is working.
If you are interested in it, have a look at the gist https://gist.github.com/mliebelt/a36ace0641e61f49d78f. You should be able to download the file, and run it directly from the command line. You have to have installed first parslet, minitest should be already included in a current Ruby version.
I have added there only the (missing) rule for newline, and added 3 unit tests to test all cases:
The root: /
A path with only one element: /my
A path with more than one element: /my/path
Works like expected, so I get two cases here:
Top element only
One or more path elements
Perhaps this may help others how to debug a situation like that.

Get autocompletion list in bash variable

I'm working with a big software project with many build targets. When typing make <tab> <tab> it shows over 1000 possible make targets.
What I want is a bash script that filters those targets by certain rules. Therefore I would like to have this list of make targets in a bash variable.
make_targets=$(???)
[do something with make_targets]
make $make_targets
It would be best if I wouldn't have to change anything with my project.
How can I get such a List?
#yuyichao created a function to get autocomplete output:
comp() {
COMP_LINE="$*"
COMP_WORDS=("$#")
COMP_CWORD=${#COMP_WORDS[#]}
((COMP_CWORD--))
COMP_POINT=${#COMP_LINE}
COMP_WORDBREAKS='"'"'><=;|&(:"
# Don't really thing any real autocompletion script will rely on
# the following 2 vars, but on principle they could ~~~ LOL.
COMP_TYPE=9
COMP_KEY=9
_command_offset 0
echo ${COMPREPLY[#]}
}
Just run comp make '' to get the results, and you can manipulate that. Example:
$ comp make ''
test foo clean
You would need to overwrite / modify the completion function for make. On Ubuntu it is located at:
/usr/share/bash-completion/completions/make
(Other distributions may store the file at /etc/bash_completion.d/make)
If you don't want to change the completion behavior for the whole system, you might write a small wrapper script like build-project, which calls make. Then write a completion function for that mapper which is derived from make's one.

Setting environment variables with puppet

I'm trying to work out the best way to set some environment variables with puppet.
I could use exec and just do export VAR=blah. However, that would only last for the current session. I also thought about just adding it onto the end of a file such as bashrc. However then I don't think there is a reliable method to check if it is all ready there; so it would end up getting added with every run of puppet.
I would take a look at this related question.
*.sh scripts in /etc/profile.d are read at user-login time (as the post says, at the same time /etc/profile is sourced)
Variables export-ed in any script placed in /etc/profile.d will therefore be available to your users.
You can then use a file resource to ensure this action is idempotent. For example:
file { "/etc/profile.d/my_test.sh":
content => 'export MYVAR="123"'
}
Or an alternate means to an indempotent result:
Example
if [[ ! grep PINTO_HOME /root/.bashrc | wc -l > 0 ]] ; then
echo "export PINTO_HOME=/opt/local/pinto" >> /root/.bashrc ;
fi
This option permits this environmental variable to be set when the presence of the
pinto application makes it warrented rather than having to compose a user's
.bash_profile regardless of what applications may wind up on the box.
If you add it to your bashrc you can check that it's in the ENV hash by doing
ENV[VAR]
Which will return => "blah"
If you take a look at Github's Boxen they source a script (/opt/boxen/env.sh) from ~/.profile. This script runs a bunch of stuff including:
for f in $BOXEN_HOME/env.d/*.sh ; do
if [ -f $f ] ; then
source $f
fi
done
These scripts, in turn, set environment variables for their respective modules.
If you want the variables to affect all users /etc/profile.d is the way to go.
However, if you want them for a specific user, something like .bashrc makes more sense.
In response to "I don't think there is a reliable method to check if it is all ready there; so it would end up getting added with every run of puppet," there is now a file_line resource available from the puppetlabs stdlib module:
"Ensures that a given line is contained within a file. The implementation matches the full line, including whitespace at the beginning and end. If the line is not contained in the given file, Puppet appends the line to the end of the file to ensure the desired state. Multiple resources can be declared to manage multiple lines in the same file."
Example:
file_line { 'sudo_rule':
path => '/etc/sudoers',
line => '%sudo ALL=(ALL) ALL',
}
file_line { 'sudo_rule_nopw':
path => '/etc/sudoers',
line => '%sudonopw ALL=(ALL) NOPASSWD: ALL',
}

A problem with folding bash functions in vim

I have a bash script file which starts with a function definition, like this:
#!/bin/bash
# .....
# .....
function test {
...
...
}
...
...
I use vim 7.2, and I have set g:sh_fold_enabled=1 such that folding is enabled with bash. The problem is that the folding of the function test is not ended correctly, i.e. it lasts until the end of file. It looks something like this:
#!/bin/bash
# .....
# .....
+-- 550 lines: function test {----------------------------------------
~
~
The function itself is just about 40 lines, and I want something that lookes like this ("images" say more than a thousend words, they say...):
#!/bin/bash
# .....
# .....
+-- 40 lines: function test {----------------------------------------
...
...
...
~
~
Does anyone know a good solution to this problem?
I have done some research, and found a way to fix the problem: To stop vim from folding functions until the end of file, I had to add a skip-statement to the syntax region for shExpr (in the file sh.vim, usually placed somewhere like /usr/share/vim/vim70/syntax/):
syn region shExpr ... start="{" skip="^function.*\_s\={" end="}" ...
This change stops the syntax file from thinking that the { and } belongs to the shExpr group, when they actually belong to the function group. Or that is how I have understood it, anyway.
Note: This fix only works for the following syntax:
function test
{
....
}
and not for this:
function test {
....
}
A quick and dirty fix for the last bug is to remove shExpr from the #shFunctionList cluster.
with vim 8.2+ the following worked for me:
syntax enable
let g:sh_fold_enabled=5
let g:is_sh=1
set filetype=on
set foldmethod=syntax
" :filteype plugin indent on
foldnestmax=3 "i use 3, change it to whatever you like.
it did not matter where i put it in my vimrc.
and this turns on syntax folding and the file type plugin for all installed file types.
It should just work, but there seems to be a bug in the syntax file. The fold region actually starts at the word 'function' and tries to continue to the closing '}', but the highlighting for the '{...}' region takes over the closing '}' and the fold continues on searching for another one. If you add another '}' you can see this in action:
function test {
...
}
}
There seems to be a simpler solution on Reddit.
To quote the author in the post:
The options I use are:
syntax=enable
filetype=sh
foldmethod=syntax
let g:sh_fold_enabled=3
g:is_sh=1
EDIT: Workaround
vim -u NONE -c 'let g:sh_fold_enabled=7' -c ':set fdm=syntax' -c 'sy
on' file.sh
g:sh_fold_enabled=4 seemed to be the agreed upon fold-level in the discussion. This solution is working perfectly for me. I did not have to edit the syntax file.
Edit: g:sh_fold_enabled=5 is actually the right one. Not 4.
Also, as the poster showed on Reddit, those commands must go before any other setting in vimrc, except the plugins.

Resources