Ruby file renamer - ruby

this is a text file renamer i made, you throw the file in a certain folder and the program renames them to file1.txt, file2.txt, etc
it gets the job done but it's got two problems
it gives me this error no implicit conversion of nil into String error
if i add new files into the folder where there's already organized files, they're all deleted and a new file is created
what's causing these problems?
i=0
Dir.chdir 'C:\Users\anon\Desktop\newfolder'
arr = Dir.entries('C:\Users\anon\Desktop\newfolder')
for i in 2..arr.count
if (File.basename(arr[i]) == 'file'+((i-1).to_s)+'.txt')
puts (arr[i]+' is already renamed to '+'file'+i.to_s)
else
File.rename(arr[i],'file'+((i-1).to_s)+'.txt')
end
end

There are two main problems in your program.
The first is that you are using an out of bounds value in the array arr. Try this a = [1,2,3]; a[a.count] and you will get nil because you are trying at access a[3] but the last element in the array has index 2.
Then, you are using as indexes for names fileINDEX.txt always 2...foobar without taking into account that some indexes may be already used in your directory.
Extra problem, you are using Dir.entries, this in my OS gives regular entries more . and .. which should be managed properly, they are not what you want to manipulate.
So, I wrote you a little script, I hope you find it readable, to me it works. You can improve it for sure! (p.s. I am under Linux OS).
# Global var only to stress its importance
$dir = "/home/p/tmp/t1"
Dir.chdir($dir)
# get list of files
fnames = Dir.glob "*"
# get the max index "fileINDEX.txt" already used in the directory
takenIndexes = []
fnames.each do |f|
if f.match /^file(\d+).txt/ then takenIndexes.push $1.to_i; end
end
# get the first free index available
firstFreeIndex = 1
firstFreeIndex = (takenIndexes.max + 1) if takenIndexes.length > 0
# get a range of fresh indexes for possible use
idxs = firstFreeIndex..(firstFreeIndex + (fnames.length))
# i transform the range to list and reverse the order because i want
# to use "pop" to get and remove them.
idxs = idxs.to_a
idxs.reverse!
# rename the files needing to be renamed
puts "--- Renamed files ----"
fnames.each do |f|
# if file has already the wanted format then move to next iteration
next if f.match /^file\d+.txt/
newName = "file" + idxs.pop.to_s + ".txt"
puts "rename: #{f} ---> #{newName}"
File.rename(f, newName)
end

Related

Custom serialize and parse methods in Ruby

I have developed this class Directory that some what emulates a directory using hashes. I have difficulties figuring out how to do the serialize and parse methods. The returned string from the serialize method should look something like this:
2:README:19:string:Hello world!spec.rb:20:string:describe RBFS1:rbfs:4:0:0:
Now to explain what exactly this means. This is the master directory and the 2 upfront means the number of files, than we have the file name README and after that the length of the contents of the file 19, represented with a string that I get from the parse method of the other class in the module. And after that the second file, also notice that the two files are not separated by :, we don't need it here since when know the string length. So in a little better look:
<file count><file1_data><file2_data>1:rbfs:4:0:0:, here <file1_data>, encompasses the name, length and contents part.
Now the 1:rbfs:4:0:0: means we have one sub-directory with name rbfs, 4 representing the length of it's contents as a string and 0:0: representing that it's empty, no file and no sub-directories. Here is another example:
0:1:directory1:40:0:1:directory2:22:1:README:9:number:420: which is equivalent to:
.
`-- directory1
`-- directory2
`-- README
I have no problem with the files part,and i know how to get the number of directories and their names, but the other part I have no idea what to do. I know that recursion is the best answer, but I have no clue what should the bottom of that recursion be and how to implement it. Also solving this will help greatly in figuring out how to do the parse method by reverse engineering it.
The code is below:
module RBFS
class File
... # here I have working `serialize` and `parse` methods for `File`
end
class Directory
attr_accessor :content
def initialize
#content = {}
end
def add_file (name,file)
#content[name]=file
end
def add_directory(name, subdirectory = nil)
if subdirectory
#content[name] = subdirectory
else
#content[name] = RBFS::Directory.new
end
end
def serialize
...?
end
def self.parse (string)
...?
end
end
end
PS: I check the kind of values in the hash with the is_a? method.
Another example for #Jordan:
2:file1:17:string:Test test?file2:10:number:4322:direc1:34:0:1:dir2:22:1:README:9:number:420:direc2::1:README2:9:number:33:0
...should be this structure (if I've formulated it right):
. ->file1,file2
`-- direc1,.....................................direc2 -> README2
`-- dir2(subdirectory of direc1) -> README
direc1 contains only a directory and no files, while direc2 contains only a file.
You can see that the master directory doesn't specify it's string length while all others do.
Okay, let's work through this iteratively, starting with your example:
str = "2:README:19:string:Hello world!spec.rb:20:string:describe RBFS1:rbfs:4:0:0:"
entries = {} # No entries yet!
The very first thing we need to know is how many files there are, and we know we know that's the number before the first ::
num_entries, rest = str.split(':', 2)
num_entries = Integer(num_entries)
# num_entries is now 2
# rest is now "README:19:string:Hello world!spec.rb:20:string:describe RBFS1:rbfs:4:0:0:"
The second argument to split says "I only want 2 pieces," so it stops splitting after the first :.) We use Integer(n) instead of n.to_i because it's stricter. (to_i will convert "10xyz" to 10; Integer will raise an error, which is what we want here.)
Now we know we have two files. We don't know anything else yet, but what's left of our string is this:
README:19:string:Hello world!spec.rb:20:string:describe RBFS1:rbfs:4:0:0:
The next thing we can get is the name and length of the first file.
name, len, rest = rest.split(':', 3)
len = Integer(len.to_i)
# name = "README"
# len = 19
# rest = "string:Hello world!spec.rb:20:string:describe RBFS1:rbfs:4:0:0:"
Cool, now we have the name and length of the first file, so we can get its content:
content = rest.slice!(0, len)
# content = "string:Hello world!"
# rest = "spec.rb:20:string:describe RBFS1:rbfs:4:0:0:"
entries[name] = content
# entries = { "README" => "string:Hello world!" }
We used rest.slice! which modifies removes len characters from the front of the string and returns them, so content is just what we want (string:Hello world!) and rest is everything that was after it. Then we added it to entries Hash. One file down, one to go!
For the second file, we do the exact same thing:
name, len, rest = rest.split(':', 3)
len = Integer(len)
# name = "spec.rb"
# len = 20
# rest = "string:describe RBFS1:rbfs:4:0:0:"
content = rest.slice!(0, len)
# content = "string:describe RBFS"
# rest = "1:rbfs:4:0:0:"
entries[name] = content
# entries = { "README" => "string:Hello world!",
# "spec.rb" => "string:describe RBFS" }
Since we do the exact same thing twice, obviously we should do this in a loop! But before we write that, we need to get organized. So far we have two discrete steps: First, get the number of files. Second, get those files' contents. We also know we'll need to get the number of directories and the directories. We'll take a guess at how this'll look:
def parse(serialized)
files, rest = parse_files(serialized)
# `files` will be a Hash of file names and their contents and `rest` will be
# the part of the string we haven't serialized yet
directories, rest = parse_directories(rest)
# `directories` will be a Hash of directory names and their contents
files.merge(directories)
end
def parse_files(serialized)
# Get the number of files from the beginning of the string
num_entries, rest = str.split(':', 2)
num_entries = Integer(num_entries)
entries = {}
# `rest` now starts with the first file (e.g. "README:19:...")
num_entries.times do
name, len, rest = rest.split(':', 3) # get the file name and length
len = Integer(len)
content = rest.slice!(0, len) # get the file contents from the beginning of the string
entries[name] = content # add it to the hash
end
[ entries, rest ]
end
def parse_directories(serialized)
# TBD...
end
That parse_files method is a bit long for my taste, though, so how about we split it up?
def parse_files(serialized)
# Get the number of files from the beginning of the string
num_entries, rest = str.split(':', 2)
num_entries = Integer(num_entries)
entries = {}
# `rest` now starts with the first file (e.g. "README:19:...")
num_entries.times do
name, content, rest = parse_file(rest)
entries[name] = content # add it to the hash
end
[ entries, rest ]
end
def parse_file(serialized)
name, len, rest = serialized.split(':', 3) # get the name and length of the file
len = Integer(len)
content = rest.slice!(0, len) # use the length to get its contents
[ name, content, rest ]
end
Clean!
Now, I'm going to give you a big spoiler: Since the serialization format is reasonably well-designed, we don't actually need a parse_directories method, because it would do exactly the same thing as parse_files. The only difference is that after this line:
name, content, rest = parse_file(rest)
...we want to do something different if we're parsing directories instead of files. In particular, we want to call parse(content), which will do all of this over again on the directory's contents. Since it's pulling double-duty now, we should probably change it's name to something more general like parse_entries, and we also need to give it another argument to tell it when to do that recursion.
Rather than post more code here, I've posted my "finished" product over in this Gist.
Now, I know that doesn't help you with the serialize part, but hopefully it'll help get you started. serialize is the easier part because there are plenty of questions and answers on SO about recursively iterating over a Hash.

Ruby splitting string into different files

Here I've created an algorithm that extracts an array of the Federalist papers and splits them up saving them into separate files titled "Federalist No." followed by their respective numbers. Everything works perfectly and the files are being created beautifully; however, the only problem I run into now is that it fails to create the last output.
Maybe it's because I've been staring at this for too many hours but I'm at an impasse.
I've inserted the line puts fedSections.length to see what the output is.
Using a smaller version of the compilation of the Fed papers for testing, the terminal output is 3... it creates "Federalist No. 0" a blank document to take into account empty space and "Federalist No. 1" with the first federalist paper. No "Federalist No. 2."
Any thoughts?
# Create new string to add array l to
fedString = " "
for f in 0...l.length-1
fedString += l[f] + ''
end
# Create variables applied to new files
Federalist_No= "Federalist No."
a = "0"
b = "FEDERALIST No."
fedSections = Array.new() # New array to insert Federalist paper to
fedSections = fedString.split("FEDERALIST No.") # Split string into elements of the array at each change in Federalist paper
puts fedSections.length
# Split gives empty string, off by one
for k in 0...fedSections.length-1 # Use of loop to write each Fed paper to its own file
new_text = File.open(Federalist_No + a + ".txt", "w") # Open said file with write capabilities
new_text.puts(b+a) # Write the "FEDERALIST No" and the number from "a"
new_text.puts fedSections[k] # Write contents of string (section of paper) to a file
new_text.close()
a = a.to_i + 1 # Increment "a" by one to accomodate for consecutive papers
a = a.to_s # Restore to string
end
The error is in your for loop
for k in 0...fedSections.length-1
you actually want
for k in 0..fedSections.length-1
... does not include the last element in the range
but as screenmutt said, it is more idiomatic ruby to use an each loop
fedSections.each do |section|

Using Ruby to automate a large directory system

So I have the following little script to make a file setup for organizing reports that we get.
#This script is to create a file structure for our survey data
require 'fileutils'
f = File.open('CustomerList.txt') or die "Unable to open file..."
a = f.readlines
x = 0
while a[x] != nil
Customer = a[x]
FileUtils.mkdir_p(Customer + "/foo/bar/orders")
FileUtils.mkdir_p(Customer + "/foo/bar/employees")
FileUtils.mkdir_p(Customer + "/foo/bar/comments")
x += 1
end
Everything seems to work before the while, but I keep getting:
'mkdir': Invalid argument - Cust001_JohnJacobSmith(JJS) (Errno::EINVAL)
Which would be the first line from the CustomerList.txt. Do I need to do something to the array entry to be considered a string? Am I mismatching variable types or something?
Thanks in advance.
The following worked for me:
IO.foreach('CustomerList.txt') do |customer|
customer.chomp!
["orders", "employees", "comments"].each do |dir|
FileUtils.mkdir_p("#{customer}/foo/bar/#{dir}")
end
end
with data like so:
$ cat CustomerList.txt
Cust001_JohnJacobSmith(JJS)
Cust003_JohnJacobSmith(JJS)
Cust002_JohnJacobSmith(JJS)
A few things to make it more like the ruby way:
Use blocks when opening a file or iterating through arrays, that way you don't need to worry about closing the file or accessing the array directly.
As noted by #inger, local vars start with lower case, customer.
When you want the value of a variable in a string usign #{} is more rubinic than concatenating with +.
Also note that we took off the trailing newline using chomp! (which changes the var in place, noted by the trailing ! on the method name)

Variable changing

I wrote some code to get input from a user and then alter it to my needs. I need it in the altered and unaltered form so I am saving the input into two variables. What I don't understand is why it both variables are changing. I tried some extra puts lines to determine what the cause is but I am unable to figure it out. The code:
puts "Enter the full directory path of the flv files."
folder = gets.chomp
puts "Folder 1: " + folder
path = folder
path.slice!(0..6)
path.gsub!('\\', '/')
path += '/'
puts "Folder: " + folder
puts "Path: " + path
With input: f:\folder\subfolder\another
Output:
Folder 1: f:\folder\subfolder\another
Folder: folder/subfolder/another
Path: folder/subfolder/another/
What I'm going for is getting a directory and keeping the directory for other processes, but also transforming it into a URL friendly format. Ideas?
path = folder # does not actually copy the object, copies the reference
path.object_id == folder.object_id # the objects are the same, see
path.slice!(0..6) # all bang methods work with the same object
Thus your path is a reference to the same object as folder.
To fix this, use
path = folder.clone
When you do b = a, it's making b point at the same value as a, so when you change a's value using something like slice!, b will also point to the changed value.
To avoid this, duplicate the object instead:
b = a.dup

Sort File List in Xcode?

Is there a way in Xcode to sort my list of files under say the Classes folder Alphabetically?
I know I can drag them around, but with tons of files that is a pain.
I am surprised I can not right click on the folder and say to sort.
Click on the folder, and then click Edit > Sort > By Name
Here is a Ruby script that will sort all the files within their respective groups in an Xcode 4 project file (probably Xcode 3 as well but I haven't tried that).
Usage:
ruby sort.rb <infile> <outfile>
where <infile> is an unsorted .pbxproj file and <output> will be the sorted version. Don't make them the same file.
#!/usr/bin/env ruby
state = :primary
group = []
file_count = group_count = 0
File.open ARGV[0] do |infile|
File.open ARGV[1], 'w' do |outfile|
infile.each_line do |line|
case state
when :primary
# copy lines until and including "children = ("
outfile.write line
state = :group if line =~ /^\s*children\s*=\s*\x28\s*$/
when :group
if line =~ /^\s*[0-9A-F]+\s*\/\* (.*) \*\/,\s*$/
# add file to current group if "<guid> /* <filename> */,"
group << [$1,line]
file_count += 1
else
# otherwise, output sorted files,
# empty the group, and go back to primary state
group.sort.each do |fn,ln|
outfile.write ln
end
state = :primary
group = []
outfile.write line
group_count += 1
end
end
end
end
end
puts "Sorted #{file_count} files in #{group_count} groups"
The ruby script from jedediah works great. To also sort resources being copied, you can add:
state = :group if line =~ /^\s*files\s*=\s*\x28\s*$/
Note that sort is case sensitive (capital letters first). To make it insensitive, use:
group << [$1.downcase,line]
There isn't really an easy solution in XCode5.
I opened the pbxproj file in a text editor.
Navigate down to /* Begin PBXResourcesBuildPhase section */
select everything in files.
copy to a new text document.
Replace /* with \t (tab character)
select all, copy and paste into blank excel document. you should have 2 columns of data
insert a column at poisition 2
make all rows for that column /*
sort the sheet on column 3
copy all data and paste back over your section in pbxproj file
save file
That should sort the "Copy Bundle Resources" section of your project.
I feel dirty just doing this, but hey - it works
Czar there are advantages to having it the way you want, instead of automatically having it sort at all times.
Some classes might be related in some way, but the names aren't right next to each other, I've used that for certain. :)

Resources