Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Function: IncrementName (create next file)


  • Please log in to reply
12 replies to this topic
PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
I wrote a little function to increment the number at the end of a filename (without extension, already removed with FileSplitPath).
I thought I could share with everybody which may need something similar:
IncrementName(nameNoExt)
{
	local d, dd

	Loop
	{
		; Take out rightmost char (digit?)
		StringRight d, nameNoExt, 1
		StringTrimRight nameNoExt, nameNoExt, 1
		If d = 9
		{
			; Propagate carry: continue
			dd = 0%dd%
		}
		Else If d between 0 and 8
		{
			; Just increment this digit
			dd := (d + 1) . dd
			Break
		}
		Else
		{
			; Not a digit: put back non digit char, add a 1
			nameNoExt = %nameNoExt%%d%1 
			Break
		}
	}
	Return nameNoExt . dd
}
Test code:
r := r . IncrementName("foo") . "`n"
r := r . IncrementName("foo0") . "`n"
r := r . IncrementName("foo5") . "`n"
r := r . IncrementName("foo9") . "`n"
r := r . IncrementName("foo10") . "`n"
r := r . IncrementName("foo15") . "`n"
r := r . IncrementName("foo19") . "`n"
r := r . IncrementName("foo111") . "`n"
r := r . IncrementName("foo199") . "`n"
r := r . IncrementName("foo999") . "`n"

r := r . IncrementName("foo000") . "`n"
r := r . IncrementName("foo005") . "`n"
r := r . IncrementName("foo009") . "`n"
r := r . IncrementName("foo010") . "`n"
r := r . IncrementName("foo015") . "`n"
r := r . IncrementName("foo019") . "`n"
MsgBox %r%
[EDIT] I integrated partly evl's and Titan' suggestions of improvement.
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
While writing this little function, I made a number of big errors, making me wonder what was going on...
Instead of keeping my shame for me, I thought I should also share my errors, as seeing them may help some people to avoid these traps...
Here is the erroneous function:
debugF = debug.txt
; Same test code

IncrementName(nameNoExt)
{
	MsgBox => %nameNoExt%`n%debugF%
	FileAppend => %nameNoExt%`n, %debugF%
	Loop
	{
		StringRight d, nameNoExt, 1
		StringTrimRight nameNoExt, nameNoExt, 1
		FileAppend %A_Index% - %d%`n, %debugF%
		If d between 0 and 8
		{
			d++
			dd = %d%%dd%
			FileAppend %A_Index% - %dd%`n, %debugF%
			Break
		}
		Else If (d = 9)
		{
			dd = 0%dd%
			FileAppend %A_Index% - %dd%`n, %debugF%
		}
		Else
		{
			; Not a digit
			nameNoExt = %nameNoExt%%d% ; put back non digit char
			; Must add a 1
			nameNoExt = %nameNoExt%1
			Break
		}
	}
	FileAppend => %nameNoExt%%dd%`n, %debugF%
	Return %nameNoExt%%dd%
}
As you can see, since the MsgBox returned empty strings, I added some internal MsgBox and FileAppend to see what didn't worked...
I was stumped to see neither messages nor new file...
Do you know what were my errors (you can guess by comparing with the correct code, so don't look right now). There are three big mistakes.

.
.
.

First error: I still get bitten by the PHP-like scope of global variables.
I forgot to put a global or local d, dd declaration on top of the function. So debugF isn't visible and the FileAppend silently fails to append to an empty filename.

Second error: I like to omit the initial comma, it gives a Visual Basic flavor... I knew that the initial comma must be present if the first parameter is omitted (a quite rare case). But I fall into a not so obvious trap: MsgBox =xxx and FileAppend =xxx are really assignments of strings to variables having the same name as the commands... So they did nothing... Bad choice of debug string prefix!

Third error: I knew that Return var and Return %var% are synonym. I am not sure where I got this information as it is not documented in the Return page... Probably seen it in a script.
Anyway, Return argument must actually be an expression, except in the above exception. I wrote Return %nameNoExt%%dd% which actually built a variable name corresponding to the concatenation of the strings, and returned the value of the variable, which was empty of course.

Note to Chris: the error message box showing lines from the faulty script have a curious problem that make me wonder if my function, returning empty string, was actually called.
For example:
x := F("z")
MsgBox %x%

F(_)
{
	r = /%_%\
	Return %r%t
}
I get:
---------------------------
test.ahk
---------------------------
Error: This variable or function name contains an illegal character.  The current thread will exit.

Specifically: /z\t

	Line#
	012: x := F("z")
	013: MsgBox,%x%
	016: {
	017: r = /%_%\
--->	018: Return,%r%t
	019: }
	020: Exit
	020: Exit

---------------------------
OK   
---------------------------
Good message, but the function name is absent!
Note also the duplicated Exit line (20)...
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

evl
  • Members
  • 1237 posts
  • Last active: Oct 20 2010 11:41 AM
  • Joined: 24 Aug 2005
Seems to be quite effective (the 1st post of course :lol: ) I managed to shorted it a little (as a learning experience for myself):

IncrementName(nameNoExt) 
{ 
   Loop 
    { 
    StringRight d, nameNoExt, 1 
    StringTrimRight nameNoExt, nameNoExt, 1 
    If (d = 9) 
      {
      dd = 0%dd% 
      Continue
      }
    If d between 0 and 8 
      dd := (d + 1) . dd  
    Else 
      nameNoExt = %nameNoExt%%d%1 ; Not a digit - put back non digit char and add a 1
    Break 
    } 
  Return nameNoExt . dd 
} 


PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
Yes, for some reason (trials and errors...), I made some redundant code.
I integrated some of your suggestions, but I prefer my flow of control. To each his owns...

While I am here, I can as well put the code using this function. It is part of my permanent script (always on):
;----------------------------------------------------------------
; Explorer hotkeys
; The Explorer class below may change depending on system
; and the way the window is created. Works for me... (Win+E in XP)
#IfWinActive ahk_class ExploreWClass

;--- Shift+F9: in Explorer, create a new folder
+F9::
	appName = Create Folder
	; Get current path in Explorer
	ControlGetText currentPath, Edit1, ahk_class ExploreWClass
	InputBox folderName, %appName%, Input new folder name
	If ErrorLevel <> 0	; Canceled
		Return
	IfNotExist %currentPath%\%folderName%
	{
		FileCreateDir %currentPath%\%folderName%
	}
	else
	{
		MsgBox 16, %appName%, A folder with this name already exists!
		Send {F5}
	}
Return

;----------------------------------------------------------------
;--- F9: in Explorer, duplicate current file
F9::
	appName = Duplicate File
	; Get current path in Explorer
	ControlGetText currentPath, Edit1, ahk_class ExploreWClass
	;--- Get current (selected/with focus) filename in Windows Explorer
	selectedFile := GetExplorerSelectedFile()
	If (selectedFile = "")
	{
		MsgBox 16, %appName%, No selected file!
		Return
	}
	InputBox wantedName, %appName%
			, Duplicate of '%selectedFile%'`nInput file suffix`n(* to increment number)`n(or full name if prefixed with /)
	If ErrorLevel <> 0	; Canceled
		Return

	SplitPath selectedFile, fileName, , ext, nameNoExt

	IfInString wantedName, /
	{
		StringReplace newName, wantedName, /
	}
	Else IfInString wantedName, *
	{
		newName := IncrementName(nameNoExt) "." ext
	}
	Else
	{
		fileSuffix := wantedName
	}
	If fileSuffix
	{
		newName = %nameNoExt%%fileSuffix%.%ext%
	}
	IfNotExist %currentPath%\%newName%
	{
		; May fail if fileSuffix contains a forbidden char... I suppose user is smart...
		If InStr(FileExist(currentPath "\" selectedFile), "D")
		{
			FileCreateDir %currentPath%\%newName%
		}
		Else
		{
			FileCopy %currentPath%\%selectedFile%, %currentPath%\%newName%
		}
	}
	else
	{
		MsgBox 16, %appName%, A file named '%newName%' already exists!
	}
;	Send {F5}
Return

#IfWinActive

; (Some other hotkeys...)


GetExplorerSelectedFile()
{
	local selectedFiles, file

	ControlGet, selectedFiles, List, Selected Col1, SysListView321, ahk_class ExploreWClass
	Loop, Parse, selectedFiles, `n  ; Rows are delimited by linefeeds (`n).
	{
		If (A_Index = 1)
		{
			file := A_LoopField
		}
		Else
		{
			; Indicate that several files are selected, we return only the first one
			ErrorLevel := A_Index
		}
	}
	Return file
}

Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

polyethene
  • Members
  • 5519 posts
  • Last active: May 17 2015 06:39 AM
  • Joined: 26 Oct 2012

Seems to be quite effective (the 1st post of course :lol: ) I managed to shorted it a little (as a learning experience for myself):

I've made further size reductions (note naming difference):
iN(n) {

	Loop {

		StringRight, d, n, 1

		StringTrimRight, n, n, 1

		If d = 9

			dd = 0%dd%

		Else {

			If (d >= 0 and d <= 8)

				dd := (d + 1) . dd

			Else

				n = %n%%d%1

			Break

		} }

	Return, n . dd

}

autohotkey.com/net Site Manager

 

Contact me by email (polyethene at autohotkey.net) or message tidbit


Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004

First error: I forgot to put a global or local d, dd declaration on top of the function.

There is a tentative plan to support "super globals", which might work by using the global keyword outside of a function. Such a global would then become visible inside all functions without having to declare it in every one.

The current thread will exit.
---> 018: Return,%r%t
Good message, but the function name is absent!

The function name is absent because this error is referring to the fact that it thinks you're trying to build a dynamic variable name that contains bad characters (in the "return" line). This error is not specific to functions.

Note also the duplicated Exit line (20)...

That is normal. It simplifies the script in memory in a way that allows runtime performance to be increased.

Thanks.

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005

The current thread will exit.
---> 018: Return,%r%t
Good message, but the function name is absent!

The function name is absent because this error is referring to the fact that it thinks you're trying to build a dynamic variable name that contains bad characters (in the "return" line). This error is not specific to functions.

What I meant is that the whole (small) script is listed, except comments at start and empty lines, of course, and except line 15, where there should be the function definition (name & params).
It is OK (probable technical reason), but look strange when you wonder if the function is really executed, or even seen...
I knew it was a bug on my side, or at least I tried hard to find a proof, to avoid making a bug report, putting the blame on AHK instead of on my own code... :-)

@Titan: I am not trying to make the shortest code, but one readable and easy to follow. Which doesn't mean your version isn't good... :-)
But that's why I put my braces on separate lines, and even around a single line.
I like the If d between 0 and 8 construct...
Oh, I will put the 9 case first, as it is apart of the two others breaking the loop. And for consistency, I drop the expression for this If.
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004

...the whole (small) script is listed... except line 15, where there should be the function definition (name & params).

Ah, thanks for clarifying. Yes, function definitions aren't shown in ListLines because they're not an executable part of the script. It would be nice to see them, so maybe this can be added someday.

toralf
  • Moderators
  • 4035 posts
  • Last active: Aug 20 2014 04:23 PM
  • Joined: 31 Jan 2005
Hi Philho,

Thanks for sharing it. Specially the history to track down the bugs might help other people.

:) Again: Some minds work the same; I coded a similar function.
Ciao
toralf
 
I use the latest AHK version (1.1.15+)
Please ask questions in forum on ahkscript.org. Why?
For online reference please use these Docs.

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
Yes, I thought I wasn't the first to make such function, but I thought it would be faster to rewrite it than to search for it (I was wrong :-P) and I had fun making it.

Thanks for reminding your function, it does much more than mine.
As you saw, I made the test for file existence outside the function and just fail if it exists. I could iterate, but I don't really need that.
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Laszlo
  • Moderators
  • 4713 posts
  • Last active: Mar 31 2012 03:17 AM
  • Joined: 14 Feb 2005
Toralf was not the first, either. There was a thread a while ago, where someone asked to generate guaranteed unique filenames. The problem with the counter approach is that at the second run you have to find the first unused filename. (Of course, if other applications write in the same directory, there could be conflicts later, too.) We concluded at that time that the simplest solution (5 lines of code) is to name the files by the current date+time (A_Now). At an unexpected conflict, just keep on trying. The time automatically increases, and eventually you find an unused filename. However, this only happens if unknown applications also write into your working directory, which is not common.

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
Good strategy. Note that my function has been created essentially to allow me to quickly duplicate a file manually in Explorer. I used to do Ctrl+drag'n'drop of file + rename (remove Copy of..., etc.), this is slightly faster.

Note that Chris could provide an access to the Ansi-C function tmpnam which is supposed to provide a unique filename without clash risk.

Well, it is a bit controversed, as you can get this filename unique at creation time, and use it only later, and some external program could create this filename in the mean time... But frankly, risks of such clash is very low, unless you run a server creating thousand of files per second...
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
I seem to remember there's a WinAPI function that also generates unique filenames. If so, DllCall would hopefully be able to call it.