I'm trying to setup a new project in F#.
I'm using FsLexYacc as a tool, and last time i used it was when the Fsharp powerpack was 'in'. The documentation on the site are not that good. It also seems to me that there is a bug with the generic type annotation 'end since it is a keyword..
but I'm in first place just copy pasting the dummi files from the page to make sure the makefile are up and running. (lexer, parser, and program)
page:
https://fsprojects.github.io/FsLexYacc/index.html
I then get the
../../FSharp/Project/src/Lexer.fsl(21,81): error FS0001: The type 'char' does not match the type 'byte'
Have tried to enforce the type by changing _ to byte that didn't help at all
Makefile:
OS=$(shell uname -s)
ifeq ($(OS),Darwin)
export AS=as -arch i386
export CC=cc -arch i386 -framework CoreFoundation -lobjc -liconv
endif
.PHONY: all clean
fsl = fslex
fsp = fsyacc
fsc = fsharpc --nologo
fsyacclib = FsLexYacc.10.2.0/build/fsyacc/netcoreapp3.1/FsLexYacc.Runtime.dll
Main = bin/Main.exe
LexerGen = src/Lexer.fs
ParserGen = src/Parser.fs
Lexer = bin/Lexer.dll
Parser = bin/Parser.dll
all: $(Main)
$(LexerGen): src/Lexer.fsl
$(fsl) src/Lexer.fsl -o $(LexerGen)
$(ParserGen): src/Parser.fsy
$(fsp) -v --module Parser src/Parser.fsy -o $(ParserGen)
$(Lexer): $(LexerGen) $(Parser)
$(fsc) -a $(LexerGen) -r $(Parser) -o $(Lexer)
$(Parser): $(ParserGen) $(Regex) $(fsyacclib)
$(fsc) -a $(ParserGen) -r $(Regex) -r $(fsyacclib) -o $(Parser)
$(Main): src/Main.fsx $(Lexer) $(Parser)
$(fsc) -a src/Main.fsx -r $(fsyacclib) -r $(Lexer) -r $(Parser) -o $(Main)
clean: rm /bin/*.dll
I tried this, but I was not able to reproduce your error. Here is what I'm doing. Is there something I'm doing differently than you?
I copied the Lexer.fsl file from the repository:
{
// Opens methods related to fslex.exe
open FSharp.Text.Lexing
let newline (lexbuf: LexBuffer<_>) =
lexbuf.StartPos <- lexbuf.StartPos.NextLine
}
// Regular expressions
let whitespace = [' ' '\t' ]
let newline = ('\n' | '\r' '\n')
rule tokenstream = parse
// --------------------------
| "hello" { Parser.HELLO }
// --------------------------
| whitespace { tokenstream lexbuf }
| newline { newline lexbuf; tokenstream lexbuf }
// --------------------------
| _ { failwith ("ParseError" + LexBuffer<_>.LexemeString lexbuf) }
| eof { Parser.EOF }
And the Parser.fsy file:
%{
%}
// The start token becomes a parser function in the compiled code:
%start start
// Regular tokens
%token HELLO
// Misc tokens
%token EOF
// This is the type of the data produced by a successful reduction of the 'start'
// symbol:
%type < int > start
%%
// These are the rules of the grammar along with the F# code of the
// actions executed as rules are reduced.
start: File end { $1 }
| end { $1 }
File:
| HELLO { 1 }
| HELLO HELLO { 2 }
// Using F# keywords for nonterminal names is okay.
end: EOF { 3 }
And run the following command to compile the two:
fsc Parser.fs Lexer.fs -r C:\[my work folder]\FsLexYacc\src\FsLexYacc.Runtime\bin\Debug\netstandard2.0\FsLexYacc.Runtime.dll
The source code in the generated Lexer.fs looks something like this:
module Lexer
# 1 "Lexer.fsl"
// Opens methods related to fslex.exe
open FSharp.Text.Lexing
let newline (lexbuf: LexBuffer<_>) =
lexbuf.StartPos <- lexbuf.StartPos.NextLine
# 12 "Lexer.fs"
let trans : uint16[] array =
[|
(* State 0 *)
[| 5us;5us;5us;5us; (* lots more here... *)|];
(* lots more states here... *)
(* State 11 *)
[| 65535us;65535us;65535us; (* lots more here... *)|];
|]
let actions : uint16[] = [|65535us;3us;1us;2us;3us;3us;4us;2us;65535us;65535us;65535us;0us;|]
let _fslex_tables = FSharp.Text.Lexing.UnicodeTables.Create(trans,actions)
let rec _fslex_dummy () = _fslex_dummy()
// Rule tokenstream
and tokenstream lexbuf =
match _fslex_tables.Interpret(0,lexbuf) with
| 0 -> (
# 17 "Lexer.fsl"
Parser.HELLO
# 49 "Lexer.fs"
)
| 1 -> (
# 19 "Lexer.fsl"
tokenstream lexbuf
# 54 "Lexer.fs"
)
| 2 -> (
# 20 "Lexer.fsl"
newline lexbuf; tokenstream lexbuf
# 59 "Lexer.fs"
)
| 3 -> (
# 22 "Lexer.fsl"
failwith ("ParseError" + LexBuffer<_>.LexemeString lexbuf)
# 64 "Lexer.fs"
)
| 4 -> (
# 23 "Lexer.fsl"
Parser.EOF
# 69 "Lexer.fs"
)
| _ -> failwith "tokenstream"
# 3000000 "Lexer.fs"
Related
I am trying to map 3 samples: SRR14724459, SRR14724473, and a combination of both SRR14724459_SRR14724473.
I have 2 rules that share a similar file type output (.bam), and even tho I am naming their wildcards different, I still get an ambiguity error:
Building DAG of jobs...
AmbiguousRuleException:
Rules map_hybrid and map are ambiguous for the file /gpfs/scratch/hpadre/snakemake_outputs/mapped_dir/SRR14724459_SRR14724473__0.9.bam.
Expected input files:
map_hybrid: /home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724459_SRR14724473_0.9/SRR14724459_R1_trimmed_SRR14724473_R1_trimmed_0.9.fastq /home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724459_SRR14724473_0.9/SRR14724459_R2_trimmed_SRR14724473_R2_trimmed_0.9.fastq
map: /home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724459_SRR14724473__0.9/SRR14724459_SRR14724473__0.9_R1_trimmed.fastq /home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724459_SRR14724473__0.9/SRR14724459_SRR14724473__0.9_R2_trimmed.fastq
From my Snakefile, here are my variables:
all_samples: ['SRR14724459', 'SRR14724473']
sample1: ['SRR14724459']
sample2: ['SRR14724473']
titration:[0.9]
This is my rule all:
rule all:
expand(MAPPED_DIR + "/{sample}.bam", sample=all_samples),
expand(MAPPED_DIR + "/{sample1}_{sample2}_{titration}.bam", zip, sample1=list_a_titrations, sample2=list_b_titrations, titration=tit_list)
This is my map rule:
rule map:
input:
r1 = TRIMMED_DIR + "/{sample}/{sample}_R1_trimmed.fastq",
r2 = TRIMMED_DIR + "/{sample}/{sample}_R2_trimmed.fastq"
output:
MAPPED_DIR + "/{sample}.bam"
threads: 28
params:
genome = HUMAN_GENOME_DIR
log:
LOG_DIR + "/map/{sample}_map.log"
benchmark:
BENCHMARK_DIR + "/map/{sample}_bwa_benchmark.txt"
wildcard_constraints:
word='[^0-9]*'
shell:
"""
bwa mem -t {threads} {params.genome} {input.r1} {input.r2} 2> {log} | samtools view -hSbo > {output}
"""
This is my map_hybrid:
rule map_hybrid:
input:
r1 = TRIMMED_DIR + "/{sample1}_{sample2}_{titration}/{sample1}_R1_trimmed_{sample2}_R1_trimmed_{titration}.fastq",
r2 = TRIMMED_DIR + "/{sample1}_{sample2}_{titration}/{sample1}_R2_trimmed_{sample2}_R2_trimmed_{titration}.fastq"
output:
MAPPED_DIR + "/{sample1}_{sample2}_{titration}.bam"
threads: 28
params:
genome = HUMAN_GENOME_DIR
log:
LOG_DIR + "/map/{sample1}_{sample2}_{titration}_map.log"
benchmark:
BENCHMARK_DIR + "/map/{sample1}_{sample2}_{titration}_bwa_benchmark.txt"
shell:
"""
set +e
bwa mem -t {threads} {params.genome} {input.r1} {input.r2} 2> {log} | samtools view -hSbo > {output}
exitcode=$?
if [ $exitcode -eq 1 ]
then
exit 1
else
exit 0
fi
"""
The expected input files SHOULD BE as so:
Expected input files:
map_hybrid: /home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724459_SRR14724473_0.9/SRR14724459_R1_trimmed_SRR14724473_R1_trimmed_0.9.fastq /home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724459_SRR14724473_0.9/SRR14724459_R2_trimmed_SRR14724473_R2_trimmed_0.9.fastq
map: /home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724459_R1_trimmed.fastq /home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724459_R2_trimmed.fastq
and also
/home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724473_R1_trimmed.fastq /home/hpadre/ngs_artifacts_proj/output_directories/trimmed_dir/SRR14724473_R2_trimmed.fastq
Your rules map and map_hybrid can both produce your desired files, for snakemake they are ambigious rules.
The names of the wildcards is irrelevant, what is relevant is whether the wildcards in both rules can match the same output filepath.
That is the case here.
While rule map_hybrid can produce the output file SRR14724459_R2_trimmed_SRR14724473_R2_trimmed_0.9.fastq where the wildcard matches are
sample1=SRR14724459
sample2=SRR14724473
the rule map can also produce this output with the wildcard match
sample=SRR14724459_R2_trimmed_SRR14724473
To prevent ambiguity you can use the wildcard_constraint, so that the {sample} wildcard only matches strings starting with SRR followed by numbers:
sample='SRR\d+'
Integrated into your rule map:
rule map:
input:
r1 = TRIMMED_DIR + "/{sample}/{sample}_R1_trimmed.fastq",
r2 = TRIMMED_DIR + "/{sample}/{sample}_R2_trimmed.fastq"
output:
MAPPED_DIR + "/{sample}.bam"
threads: 28
params:
genome = HUMAN_GENOME_DIR
log:
LOG_DIR + "/map/{sample}_map.log"
benchmark:
BENCHMARK_DIR + "/map/{sample}_bwa_benchmark.txt"
wildcard_constraints:
word='[^0-9]*',
sample='SRR\d+'
shell:
"""
bwa mem -t {threads} {params.genome} {input.r1} {input.r2} 2> {log} | samtools view -hSbo > {output}
"""
it should resolve the ambiguity.
I have few fq.gz files for few samples. I am trying to process all samples at once using nextflow. But somehow, I am unable to process all the samples at once . But I can process a single sample at once. Here is the data structure and my code for processing single sample.
My nextflow code
params.sampleName="sample1"
params.fastq_path = "data/${params.sampleName}/*{1,2}.fq.gz"
fastq_files = Channel.fromFilePairs(params.fastq_path)
params.ref = "ab.fa"
ref = file(params.ref)
process foo {
input:
set pairId, file(reads) from fastq_files
output:
file("${pairId}.bam") into bamFiles_ch
script:
"""
echo ${reads[0].toRealPath().getParent().baseName}
bwa-mem2 mem -t 8 ${ref} ${reads[0].toRealPath()} ${reads[1].toRealPath()} | samtools sort -#8 -o ${pairId}.bam
samtools index -#8 ${pairId}.bam
"""
}
process samToolsMerge {
publishDir "./aligned_minimap/", mode: 'copy', overwrite: 'false'
input:
file bamFile from bamFiles_ch.collect()
output:
file("**")
script:
"""
samtools merge ${params.sampleName}.bam ${bamFile}
samtools index -# 8 ${params.sampleName}.bam
"""
}
So need help to solve. Thanks in advance.
It looks like you've already built in a way to set your target sample name using:
params.sampleName="sample1"
params.fastq_path = "data/${params.sampleName}/*{1,2}.fq.gz"
To have the glob pattern match all samples, you could simply set the wildcard on the command line using:
nextflow run main.nf --sampleName '*'
Note the quotation marks above. If these are ignored, the glob star will be expanded by your shell before it is passed to your Nextflow command.
The short answer is that you need some easy way to extract the sample name from the parent directory. Then you need some way to group the coordinate-sorted BAMs by the sample name. Below, I've used the new Nextflow DSL 2 but it's not strictly necessary. I just find the new DSL 2 code a lot easier to read and debug. Below is just an example, and you'll need to adapt it to suit your exact use case, but that said, it should do very similar things. It uses a special groupKey so that we can dynamically specify the expected number of elements in each tuple prior to calling the groupTuple operator. This lets us stream the collected values as soon as possible so that each sample can 'merge' when all of it's readgroups have been aligned. Without this, all input readgroups would need to finish alignment before the merge could begin.
Contents of nextflow.config:
process {
shell = [ '/bin/bash', '-euo', 'pipefail' ]
}
Contents of main.nf:
nextflow.enable.dsl=2
params.ref_fasta = "GRCh38.primary_assembly.genome.chr22.fa.gz"
params.fastq_files = "data/*/*.read{1,2}.fastq.gz"
process bwa_index {
conda 'bwa-mem2'
input:
path fasta
output:
path "${fasta}.{0123,amb,ann,bwt.2bit.64,pac}"
"""
bwa-mem2 index "${fasta}"
"""
}
process bwa_mem2 {
tag { [sample, readgroup].join(':') }
conda 'bwa-mem2 samtools'
input:
tuple val(sample), val(readgroup), path(reads)
path bwa_index
output:
tuple val(sample), val(readgroup), path("${readgroup}.bam{,.bai}")
script:
def idxbase = bwa_index.first().baseName
def out_files = [ "${readgroup}.bam", "${readgroup}.bam.bai" ].join('##idx##')
def (r1, r2) = reads
"""
bwa-mem2 mem \\
-R '#RG\\tID:${readgroup}\\tSM:${sample}' \\
-t ${task.cpus} \\
"${idxbase}" \\
"${r1}" \\
"${r2}" |
samtools sort \\
--write-index \\
-# ${task.cpus} \\
-o "${out_files}"
"""
}
process samtools_merge {
tag { sample }
conda 'samtools'
input:
tuple val(sample), path(indexed_bam_files)
output:
tuple val(sample), path("${sample}.bam{,.bai}")
script:
def out_files = [ "${sample}.bam", "${sample}.bam.bai" ].join('##idx##')
def input_bam_files = indexed_bam_files
.findAll { it.name.endsWith('.bam') }
.collect { /"${it}"/ }
.join(' \\\n'+' '*8)
"""
samtools merge \\
--write-index \\
-o "${out_files}" \\
${input_bam_files}
"""
}
workflow {
ref_fasta = file( params.ref_fasta )
bwa_index( ref_fasta )
Channel.fromFilePairs( params.fastq_files ) \
| map { readgroup, reads ->
def (sample_name) = reads*.parent.baseName as Set
tuple( sample_name, readgroup, reads )
} \
| groupTuple() \
| map { sample, readgroups, reads ->
tuple( groupKey(sample, readgroups.size()), readgroups, reads )
} \
| transpose() \
| set { sample_readgroups }
bwa_mem2( sample_readgroups, bwa_index.out )
sample_readgroups \
| join( bwa_mem2.out, by: [0,1] ) \
| map { sample_key, readgroup, reads, indexed_bam ->
tuple( sample_key, indexed_bam )
} \
| groupTuple() \
| map { sample_key, indexed_bam_files ->
tuple( sample_key.toString(), indexed_bam_files.flatten() )
} \
| samtools_merge
}
Run Like:
nextflow run -ansi-log false main.nf
Results:
N E X T F L O W ~ version 21.04.3
Launching `main.nf` [zen_gautier] - revision: dcde9efc8a
Creating Conda env: bwa-mem2 [cache /home/steve/working/stackoverflow/69702077/work/conda/env-8cc153b2eb20a5374bf435019a61c21a]
[63/73c96b] Submitted process > bwa_index
Creating Conda env: bwa-mem2 samtools [cache /home/steve/working/stackoverflow/69702077/work/conda/env-5c358e413a5318c53a45382790eecbd4]
[52/6a92d3] Submitted process > bwa_mem2 (HBR:HBR_Rep2_ERCC-Mix2_Build37-ErccTranscripts-chr22)
[8b/535b21] Submitted process > bwa_mem2 (UHR:UHR_Rep3_ERCC-Mix1_Build37-ErccTranscripts-chr22)
[dc/03d949] Submitted process > bwa_mem2 (UHR:UHR_Rep1_ERCC-Mix1_Build37-ErccTranscripts-chr22)
[e4/bfd08b] Submitted process > bwa_mem2 (HBR:HBR_Rep1_ERCC-Mix2_Build37-ErccTranscripts-chr22)
[d5/e2aa27] Submitted process > bwa_mem2 (UHR:UHR_Rep2_ERCC-Mix1_Build37-ErccTranscripts-chr22)
[c2/23ce8a] Submitted process > bwa_mem2 (HBR:HBR_Rep3_ERCC-Mix2_Build37-ErccTranscripts-chr22)
Creating Conda env: samtools [cache /home/steve/working/stackoverflow/69702077/work/conda/env-912cee20caec78e112a5718bb0c00e1c]
[28/006c03] Submitted process > samtools_merge (HBR)
[3b/51311c] Submitted process > samtools_merge (UHR)
I have:
SIESTA_ARCH = unknown
CC = gcc
FPP = $(FC) -E -P -x c
FC = gfortran
and I want to replace this by
SIESTA_ARCH = amd64 (x86_64)
CC = mpicc
FPP = $(FC) -E -P -x c
FC = mpif90
I guess next solution is working for you (edited solution according to answers of the PO):
script.sed
#!/bin/sed -f
/^SIESTA_ARCH = unknown/,/^FC =/{
s/^SIESTA_ARCH =.*/SIESTA_ARCH = amd64 (x86_64)/
s/^CC =.*/CC = mpicc/
s/^FC =.*/FC = mpif90/
}
Invoke as ./script.sed Makefile to see the results on the standard output or as ./script.sed -i Makefile to update the file Makefile.
This solution will change all the occurences of SIESTA_ARCH = unknown and the next line block until a line beginning with FC = into the new values.
In bash you can define a function like this (just execute this one-liner in a terminal or script):
function repl() { FIND="$2" REPLACE="$3" ruby -p -i -e "gsub(ENV['FIND'], ENV['REPLACE'])" "$1"; }
Then you can replace whatever literal strings you want in whatever file, e.g.:
repl ~/Code/Makefile 'SIESTA_ARCH = unknown' 'SIESTA_ARCH = amd64 (x86_64)'
repl ~/Code/Makefile 'CC = gcc' 'CC = mpicc'
repl ~/Code/Makefile 'FC = gfortran' 'FC = mpif90'
Note that this will replace all occurrences of such strings in the specified file.
If you have ed
cat script.ed
H
g/^\(SIESTA_ARCH =\)\(.\+\)$/s//\1 amd64 (x86_64)/
g/^\(CC =\)\(.\+\)$/s//\1 mpicc/
,p
Q
Using the script against your file.
ed -s Makefile < script.ed
Output
SIESTA_ARCH = amd64 (x86_64)
CC = mpicc
FPP = $(FC) -E -P -x c
FC = gfortran
Now change ,p Q to w and q To edit the file in-place.
H
g/^\(SIESTA_ARCH =\)\(.\+\)$/s//\1 amd64 (x86_64)/
g/^\(CC =\)\(.\+\)$/s//\1 mpicc/
w
q
ed -s Makefile < script.ed
I need to Validate the ID with pattern (Abbbbb-yyy)
Example :
ID := A12345-789 B98765-123 C58730-417
VARIANT := test1 test2 test3
Build and post processing will generate files depends up on VARIANTS :
`sw_main_test1.hex ,sw_main_test1.hex and sw_main_test1.hex `
.PHONY : SW_TEST
SW_TEST :
if <ID is correct>
cp sw_main_test1.hex --> A12345-789.hex
cp sw_main_test2.hex --> B98765-123.hex
cp sw_main_test3.hex --> C58730-417.hex
I am facing issue in validating the ID with pattern
`Abbbbb-yyy.txt`
Where : A=[A-Z]; b=[0-9]; y=[0-9]
Please let me know how to verify ID is correct using regular expressions inside the Makefile using any tool or utility
In this script, I assume, you get your ID from a file (I called it here someidcontent.txt). Then you could write a script like this (assuming, you only working on Linux).
getID = $(shell cat someidcontent.txt)
all:
if [ "$(getID)" == "1234567890" ]; then \
cp -v output.txt ./delivery/$(getID).txt; \
fi
.PHONY: all
Edit
I made a mistake in my previous script. I did not check, if the ID is correct. Now my newer script does this: I read from a file the ID and check it for correctness. If ID is correct, then some file will be copied into target dir with ID number.
# get ID from a file
getID := $(shell cat someidcontent.txt)
# need a hack for successful checking
idToCheck := $(getID)
# check procedure
checkID := $(shell echo $(idToCheck) | grep "[A-Z][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9]$$")
all:
ifeq "$(checkID)" "$(idToCheck)"
echo found
cp -v output.txt ./delivery/$(idToCheck).txt;
endif
.PHONY: all
Edit 2
Ok, this was a little bit challenging, but I solved it somehow. Maybe there are also other ways to solve this better. In my solution, I assume that the file with IDs and source filenames look like this (in other words, this is the content of my someidcontent.txt):
A2345-678:output1.txt
B3456-123:output.txt
C0987-987:thirdfile.txt
And this is my makefile with comments for additional explanation. I hope, they are sufficient
# retrieve id and filename data from other file
listContent := $(shell cat someidcontent.txt)
# extract only IDs from other files
checkIDs = $(shell echo $(listContent) | grep -o "[A-Z][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9]")
all:
# iterate only over IDs
# first, give me the ID
# second retrieve the filename part for successful copy procedure
# and copy the file to the target dir with ID as filename
#$(foreach x,$(checkIDs), \
echo $(x); \
cp -v $(shell echo $(listContent) | grep -o "$(x):[A-Z0-9a-z\.]*" | sed "s/[-A-Z0-9]*://g") ./delivery/$(x).t$
)
.PHONY: all
You can check simple string patterns quite ok (don't want to say "nicely") from within make:
[A-F] := A B C D E F#
[a-f] := a b c d e f#
[A-Z] := $([A-F]) G H I J K L M N O P Q R S T U V W X Y Z#
[a-z] := $([a-f]) g h i j k l m n o p q r s t u v w x y z#
[0-9] := 0 1 2 3 4 5 6 7 8 9#
######################################################################
##### $(call explode,_stringlist_,_string_)
## Insert a blank after every occurrence of the strings from _stringlist_ in _string_.
## This function serves mainly to convert a string into a list.
## Example: `$(call explode,0 1 2 3 4 5 6 7 8 9,0xl337c0de)` --> `0 xl3 3 7 c0 de`
explode = $(if $1,$(subst $(firstword $1),$(firstword $1) ,$(call explode,$(wordlist 2,255,$1),$2)),$2)
ID := A12345-789 B98765-123 C58730-417 123456+328
############################################################
# $(call check-id,_id-string_)
# Return 'malformed' or the given id
check-id = $(if $(call check-id-1,$(call explode,- $([A-Z]) $([0-9]),$1)),malformed,$1)
check-id-1 = $(strip $(filter-out $([A-Z]),$(wordlist 1,1,$1)) $(filter-out $([0-9]),$(wordlist 2,6,$1)) $(filter-out -,$(word 7,$1)) $(filter-out $([0-9]),$(wordlist 8,10,$1)) )
$(info $(foreach w,$(ID),$(call check-id,$(w))))
Xcode allows you to create automated scripts for performing repetitive tasks. What scripts have you written to speed up development?
I've created three for my JSON.Framework for Cocoa and the iPhone. These take care of the following:
Creates a release Disk Image with dynamic embedded Framework, custom iPhone SDK, API documentation and some documentation files in.
Run Doxygen over the source to create an Xcode-compatible documentation set and install this. This means when you search for things in Xcode's documentation search your documentation can be found too.
Run Doxygen over the source to update a checked-in version of the API documentation in the source tree itself. This is pretty neat if you use Subversion (which it assumes) as the documentation is always up-to-date for the branch you're in. Great if you're hosting on Google Code, for example.
Beware some hard-coded project-specific values in the below. I didn't want to potentially break the scripts by editing those out. These are launched from a Custom Script Phase in Xcode. You can see how they're integrated in the Xcode project for the project linked above.
CreateDiskImage.sh:
#!/bin/sh
set -x
# Determine the project name and version
VERS=$(agvtool mvers -terse1)
# Derived names
VOLNAME=${PROJECT}_${VERS}
DISK_IMAGE=$BUILD_DIR/$VOLNAME
DISK_IMAGE_FILE=$INSTALL_DIR/$VOLNAME.dmg
# Remove old targets
rm -f $DISK_IMAGE_FILE
test -d $DISK_IMAGE && chmod -R +w $DISK_IMAGE && rm -rf $DISK_IMAGE
mkdir -p $DISK_IMAGE
# Create the Embedded framework and copy it to the disk image.
xcodebuild -target JSON -configuration Release install || exit 1
cp -p -R $INSTALL_DIR/../Frameworks/$PROJECT.framework $DISK_IMAGE
IPHONE_SDK=2.2.1
# Create the iPhone SDK directly in the disk image folder.
xcodebuild -target libjson -configuration Release -sdk iphoneos$IPHONE_SDK install \
ARCHS=armv6 \
DSTROOT=$DISK_IMAGE/SDKs/JSON/iphoneos.sdk || exit 1
sed -e "s/%PROJECT%/$PROJECT/g" \
-e "s/%VERS%/$VERS/g" \
-e "s/%IPHONE_SDK%/$IPHONE_SDK/g" \
$SOURCE_ROOT/Resources/iphoneos.sdk/SDKSettings.plist > $DISK_IMAGE/SDKs/JSON/iphoneos.sdk/SDKSettings.plist || exit 1
xcodebuild -target libjson -configuration Release -sdk iphonesimulator$IPHONE_SDK install \
ARCHS=i386 \
DSTROOT=$DISK_IMAGE/SDKs/JSON/iphonesimulator.sdk || exit 1
sed -e "s/%PROJECT%/$PROJECT/g" \
-e "s/%VERS%/$VERS/g" \
-e "s/%IPHONE_SDK%/$IPHONE_SDK/g" \
$SOURCE_ROOT/Resources/iphonesimulator.sdk/SDKSettings.plist > $DISK_IMAGE/SDKs/JSON/iphonesimulator.sdk/SDKSettings.plist || exit 1
# Allow linking statically into normal OS X apps
xcodebuild -target libjson -configuration Release -sdk macosx10.5 install \
DSTROOT=$DISK_IMAGE/SDKs/JSON/macosx.sdk || exit 1
# Copy the source verbatim into the disk image.
cp -p -R $SOURCE_ROOT/Source $DISK_IMAGE/$PROJECT
rm -rf $DISK_IMAGE/$PROJECT/.svn
# Create the documentation
xcodebuild -target Documentation -configuration Release install || exit 1
cp -p -R $INSTALL_DIR/Documentation/html $DISK_IMAGE/Documentation
rm -rf $DISK_IMAGE/Documentation/.svn
cat <<HTML > $DISK_IMAGE/Documentation.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<script type="text/javascript">
<!--
window.location = "Documentation/index.html"
//-->
</script>
</head>
<body>
<p>Aw, shucks! I tried to redirect you to the api documentation but obviously failed. Please find it yourself. </p>
</body>
</html>
HTML
cp -p $SOURCE_ROOT/README $DISK_IMAGE
cp -p $SOURCE_ROOT/Credits.rtf $DISK_IMAGE
cp -p $SOURCE_ROOT/Install.rtf $DISK_IMAGE
cp -p $SOURCE_ROOT/Changes.rtf $DISK_IMAGE
hdiutil create -fs HFS+ -volname $VOLNAME -srcfolder $DISK_IMAGE $DISK_IMAGE_FILE
InstallDocumentation.sh:
#!/bin/sh
# See also http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
set -x
VERSION=$(agvtool mvers -terse1)
DOXYFILE=$DERIVED_FILES_DIR/doxygen.config
DOXYGEN=/Applications/Doxygen.app/Contents/Resources/doxygen
DOCSET=$INSTALL_DIR/Docset
rm -rf $DOCSET
mkdir -p $DOCSET || exit 1
mkdir -p $DERIVED_FILES_DIR || exit 1
if ! test -x $DOXYGEN ; then
echo "*** Install Doxygen to get documentation generated for you automatically ***"
exit 1
fi
# Create a doxygen configuration file with only the settings we care about
$DOXYGEN -g - > $DOXYFILE
cat <<EOF >> $DOXYFILE
PROJECT_NAME = $FULL_PRODUCT_NAME
PROJECT_NUMBER = $VERSION
OUTPUT_DIRECTORY = $DOCSET
INPUT = $SOURCE_ROOT/Source
FILE_PATTERNS = *.h *.m
HIDE_UNDOC_MEMBERS = YES
HIDE_UNDOC_CLASSES = YES
HIDE_UNDOC_RELATIONS = YES
REPEAT_BRIEF = NO
CASE_SENSE_NAMES = YES
INLINE_INHERITED_MEMB = YES
SHOW_FILES = NO
SHOW_INCLUDE_FILES = NO
GENERATE_LATEX = NO
GENERATE_HTML = YES
GENERATE_DOCSET = YES
DOCSET_FEEDNAME = "$PROJECT.framework API Documentation"
DOCSET_BUNDLE_ID = org.brautaset.$PROJECT
EOF
# Run doxygen on the updated config file.
# doxygen creates a Makefile that does most of the heavy lifting.
$DOXYGEN $DOXYFILE
# make will invoke docsetutil. Take a look at the Makefile to see how this is done.
make -C $DOCSET/html install
# Construct a temporary applescript file to tell Xcode to load a docset.
rm -f $TEMP_DIR/loadDocSet.scpt
cat <<EOF > $TEMP_DIR/loadDocSet.scpt
tell application "Xcode"
load documentation set with path "/Users/$USER/Library/Developer/Shared/Documentation/DocSets/org.brautaset.${PROJECT}.docset/"
end tell
EOF
# Run the load-docset applescript command.
osascript $TEMP_DIR/loadDocSet.scpt
RegenerateDocumentation.sh:
#!/bin/sh
# See also http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
set -x
VERSION=$(agvtool mvers -terse1)
DOXYFILE=$DERIVED_FILES_DIR/doxygen.config
DOXYGEN=/Applications/Doxygen.app/Contents/Resources/doxygen
DOCSET=$INSTALL_DIR/Documentation
APIDOCDIR=$SOURCE_ROOT/documentation
rm -rf $DOCSET
mkdir -p $DOCSET || exit 1
mkdir -p $DERIVED_FILES_DIR || exit 1
if ! test -x $DOXYGEN ; then
echo "*** Install Doxygen to get documentation generated for you automatically ***"
exit 1
fi
# Create a doxygen configuration file with only the settings we care about
$DOXYGEN -g - > $DOXYFILE
cat <<EOF >> $DOXYFILE
PROJECT_NAME = $FULL_PRODUCT_NAME
PROJECT_NUMBER = $VERSION
OUTPUT_DIRECTORY = $DOCSET
INPUT = $SOURCE_ROOT/Source
FILE_PATTERNS = *.h *.m
HIDE_UNDOC_MEMBERS = YES
HIDE_UNDOC_CLASSES = YES
HIDE_UNDOC_RELATIONS = YES
REPEAT_BRIEF = NO
CASE_SENSE_NAMES = YES
INLINE_INHERITED_MEMB = YES
SHOW_FILES = NO
SHOW_INCLUDE_FILES = NO
GENERATE_LATEX = NO
GENERATE_HTML = YES
GENERATE_DOCSET = NO
EOF
# Run doxygen on the updated config file.
$DOXYGEN $DOXYFILE
# Replace the old dir with the newly generated one.
rm -f $APIDOCDIR/*
cp -p $DOCSET/html/* $APIDOCDIR
cd $APIDOCDIR
# Revert files that differ only in the timestamp.
svn diff *.html | diffstat | awk '$3 == 2 { print $1 }' | xargs svn revert
# Add/remove files from subversion.
svn st | awk '
$1 == "?" { print "svn add", $2 }
$1 == "!" { print "svn delete", $2 }
' | sh -
svn propset svn:mime-type text/html *.html
svn propset svn:mime-type text/css *.css
svn propset svn:mime-type image/png *.png
svn propset svn:mime-type image/gif *.gif
This is an improvement of the "Create Property and Synths for instance variables" script that Lawrence Johnston posted above.
Settings:
Input: Entire Document
Directory: Home Directory
Output: Discard Output
Errors: Ignore Errors (or Alert if you want to see them)
Select any number of variables and it'll create properties and syns for all of them. It'll even create/edit your dalloc method as necessary.
Edit up the results if they are not exactly right (copy vs. retain, etc.)
Handles more things like underbar storage name, behavior, dealloc,…
Link to where this comes from and discussion: http://cocoawithlove.com/2008/12/instance-variable-to-synthesized.html
#! /usr/bin/perl -w
# Created by Matt Gallagher on 20/10/08.
# Copyright 2008 Matt Gallagher. All rights reserved.
#
# Enhancements by Yung-Luen Lan and Mike Schrag on 12/08/09.
# (mainly: multiple lines)
# Copyright 2009 Yung-Luen Lan and Mike Schrag. All rights reserved.
#
# Enhancements by Pierre Bernard on 20/09/09.
# (mainly: underbar storage name, behavior, dealloc,…)
# Copyright 2009 Pierre Bernard. All rights reserved.
#
# Permission is given to use this source code file without charge in any
# project, commercial or otherwise, entirely at your risk, with the condition
# that any redistribution (in part or whole) of source code must retain
# this copyright and permission notice. Attribution in compiled projects is
# appreciated but not required.
use strict;
# Get the header file contents from Xcode user scripts
my $headerFileContents = <<'HEADERFILECONTENTS';
%%%{PBXAllText}%%%
HEADERFILECONTENTS
# Get the indices of the selection from Xcode user scripts
my $selectionStartIndex = %%%{PBXSelectionStart}%%%;
my $selectionEndIndex = %%%{PBXSelectionEnd}%%%;
# Find the closing brace (end of the class variables section)
my $remainderOfHeader = substr $headerFileContents, $selectionEndIndex;
my $indexAfterClosingBrace = $selectionEndIndex + index($remainderOfHeader, "\n}\n") + 3;
if ($indexAfterClosingBrace == -1)
{
exit 1;
}
# Get path of the header file
my $implementationFilePath = "%%%{PBXFilePath}%%%";
my $headerFilePath = $implementationFilePath;
# Look for an implemenation file with a ".m" or ".mm" extension
$implementationFilePath =~ s/\.[hm]*$/.m/;
if (!(-e $implementationFilePath))
{
$implementationFilePath =~ s/.m$/.mm/;
}
# Stop now if the implementation file can't be found
if (!(-e $implementationFilePath))
{
exit 1;
}
my $propertyDeclarations = '';
my $synthesizeStatements = '';
my $releaseStatements = '';
# Handle subroutine to trim whitespace off both ends of a string
sub trim
{
my $string = shift;
$string =~ s/^\s*(.*?)\s*$/$1/;
return $string;
}
# Get the selection out of the header file
my $selectedText = substr $headerFileContents, $selectionStartIndex, ($selectionEndIndex - $selectionStartIndex);
$selectedText = trim $selectedText;
my $selectedLine;
foreach $selectedLine (split(/\n+/, $selectedText)) {
my $type = '';
my $asterisk = '';
my $name = '';
my $behavior = '';
# Test that the selection is:
# At series of identifiers (the type name and access specifiers)
# Possibly an asterisk
# Another identifier (the variable name)
# A semi-colon
if (length($selectedLine) && ($selectedLine =~ /([_A-Za-z][_A-Za-z0-9]*\s*)+([\s\*]+)([_A-Za-z][_A-Za-z0-9]*);/))
{
$type = $1;
$type = trim $type;
$asterisk = $2;
$asterisk = trim $asterisk;
$name = $3;
$behavior = 'assign';
if (defined($asterisk) && length($asterisk) == 1)
{
if (($type eq 'NSString') || ($type eq 'NSArray') || ($type eq 'NSDictionary') || ($type eq 'NSSet'))
{
$behavior = 'copy';
}
else
{
if (($name =~ /Delegate/) || ($name =~ /delegate/) || ($type =~ /Delegate/) || ($type =~ /delegate/))
{
$behavior = 'assign';
}
else
{
$behavior = 'retain';
}
}
}
else
{
if ($type eq 'id')
{
$behavior = 'copy';
}
$asterisk = '';
}
}
else
{
next;
}
my $storageName = '';
if ($name =~ /_([_A-Za-z][_A-Za-z0-9]*)/) {
$storageName = $name;
$name = $1;
}
# Create and insert the propert declaration
my $propertyDeclaration = "\#property (nonatomic, $behavior) $type " . $asterisk . $name . ";\n";
$propertyDeclarations = $propertyDeclarations . $propertyDeclaration;
# Create and insert the synthesize statement
my $synthesizeStatement = '';
if (length($storageName))
{
$synthesizeStatement = "\#synthesize $name = $storageName;\n";
}
else
{
$synthesizeStatement = "\#synthesize $name;\n";
}
$synthesizeStatements = $synthesizeStatements . $synthesizeStatement;
# Create and insert release statement
my $releaseName = $name;
my $releaseStatement = '';
if (length($storageName))
{
$releaseName = $storageName;
}
if ($behavior eq 'assign')
{
if ($type eq 'SEL')
{
$releaseStatement = "\t$releaseName = NULL;\n";
}
}
else
{
$releaseStatement = "\t[$releaseName release];\n\t$releaseName = nil;\n";
}
$releaseStatements = $releaseStatements . $releaseStatement;
}
my $leadingNewline = '';
my $trailingNewline = '';
# Determine if we need to add a newline in front of the property declarations
if (substr($headerFileContents, $indexAfterClosingBrace, 1) eq "\n")
{
$indexAfterClosingBrace += 1;
$leadingNewline = '';
}
else
{
$leadingNewline = "\n";
}
# Determine if we need to add a newline after the property declarations
if (substr($headerFileContents, $indexAfterClosingBrace, 9) eq '#property')
{
$trailingNewline = '';
}
else
{
$trailingNewline = "\n";
}
substr($headerFileContents, $indexAfterClosingBrace, 0) = $leadingNewline . $propertyDeclarations . $trailingNewline;
my $replaceFileContentsScript = <<'REPLACEFILESCRIPT';
on run argv
set fileAlias to POSIX file (item 1 of argv)
set newDocText to (item 2 of argv)
tell application "Xcode"
set doc to open fileAlias
set text of doc to (text 1 thru -2 of newDocText)
end tell
end run
REPLACEFILESCRIPT
# Use Applescript to replace the contents of the header file
# (I could have used the "Output" of the Xcode user script instead)
system 'osascript', '-e', $replaceFileContentsScript, $headerFilePath, $headerFileContents;
my $getFileContentsScript = <<'GETFILESCRIPT';
on run argv
set fileAlias to POSIX file (item 1 of argv)
tell application "Xcode"
set doc to open fileAlias
set docText to text of doc
end tell
return docText
end run
GETFILESCRIPT
# Get the contents of the implmentation file
open(SCRIPTFILE, '-|') || exec 'osascript', '-e', $getFileContentsScript, $implementationFilePath;
my $implementationFileContents = do {local $/; <SCRIPTFILE>};
close(SCRIPTFILE);
# Look for the class implementation statement
if (length($implementationFileContents) && ($implementationFileContents =~ /(\#implementation [_A-Za-z][_A-Za-z0-9]*\n)/))
{
my $matchString = $1;
my $indexAfterMatch = index($implementationFileContents, $matchString) + length($matchString);
# Determine if we want a newline before the synthesize statement
if (substr($implementationFileContents, $indexAfterMatch, 1) eq "\n")
{
$indexAfterMatch += 1;
$leadingNewline = '';
}
else
{
$leadingNewline = "\n";
}
# Determine if we want a newline after the synthesize statement
if (substr($implementationFileContents, $indexAfterMatch, 11) eq '#synthesize')
{
$trailingNewline = '';
}
else
{
$trailingNewline = "\n";
}
substr($implementationFileContents, $indexAfterMatch, 0) = $leadingNewline. $synthesizeStatements . $trailingNewline;
if ($implementationFileContents =~ /([ \t]*\[.*super.*dealloc.*\].*;.*\n)/)
{
my $deallocMatch = $1;
my $indexAfterDeallocMatch = index($implementationFileContents, $deallocMatch);
substr($implementationFileContents, $indexAfterDeallocMatch, 0) = "$releaseStatements\n";
}
elsif ($implementationFileContents =~ /(\#synthesize .*\n)*(\#synthesize [^\n]*\n)/s) {
my $synthesizeMatch = $2;
my $indexAfterSynthesizeMatch = index($implementationFileContents, $synthesizeMatch) + length($synthesizeMatch);
my $deallocMethod = "\n- (void)dealloc\n{\n$releaseStatements\n\t[super dealloc];\n}\n";
substr($implementationFileContents, $indexAfterSynthesizeMatch, 0) = $deallocMethod;
}
# Use Applescript to replace the contents of the implementation file in Xcode
system 'osascript', '-e', $replaceFileContentsScript, $implementationFilePath, $implementationFileContents;
}
exit 0;
Here's one to log a method and its arguments any time it's executed (Select the method definition up through the lie with the opening brace and execute the script). If FIXME shows up in the output it means it's an unrecognized type. You can either add it to the script or choose the proper format specifier manually.
#!/usr/bin/python
# LogMethod
# Selection
# Selection
# Insert after Selection
# Display in Alert
import sys
import re
input = sys.stdin.read()
methodPieces = re.findall("""(\w*:)""", input)
vars = re.findall(""":\(([^)]*)\)[ ]?(\w*)""", input)
outputStrings = ["\n NSLog(#\""]
# Method taking no parameters
if not methodPieces:
outputStrings.append(re.findall("""(\w*)[ ]?{""", input)[0])
for (methodPiece, var) in zip(methodPieces, vars):
type = var[0]
outputStrings.append(methodPiece)
if "**" in type:
outputStrings.append("%p")
elif "*" in type:
if "char" in type:
outputStrings.append("%c")
else:
outputStrings.append("%#")
else:
if "int" in type or "NSInteger" in type or "BOOL" in type:
outputStrings.append("%i")
elif "NSUInteger" in type:
outputStrings.append("%u")
elif "id" in type:
outputStrings.append("%#")
elif "NSTimeInterval" in type:
outputStrings.append("%f")
elif "SEL" in type:
outputString.append("%s")
else:
outputStrings.append('"FIXME"')
if not methodPiece == methodPieces[-1]:
outputStrings.append('\\n"\n #"')
outputStrings.append("\"")
for var in vars:
name = var[1]
outputStrings.append(",\n ")
outputStrings.append(name)
outputStrings.append(");")
print "".join(outputStrings),
Here's one to create a -description method for a class. Highlight the instance variables declaration section (#interface ... { ... }) and execute the script. Then paste the result into your implementation. I use this one along with po objectName in GDB. If FIXME shows up in the output it means it's an unrecognized type. You can either add it to the script or choose the proper format specifier manually.
#!/usr/bin/python
# Create description method for class
# Selection
# Selection
# Insert after Selection
# Display in Alert
import sys
import re
input = sys.stdin.read()
className = re.findall("""(?:#interface )(\w*)""", input)[0]
vars = re.findall("""(\w*[ ][*]?)(\w*?)_?;""", input)
outputStrings = ["- (NSString *)description {\n"]
outputStrings.append("""return [NSString stringWithFormat:#"%s :\\n"\n#" -""" % className)
for type, var in vars:
outputStrings.append("%s:" % var)
if "**" in type:
outputStrings.append("%p")
elif "*" in type:
if "char" in type:
outputStrings.append("%c")
else:
outputStrings.append("%#")
else:
if "int" in type or "NSInteger" in type or "BOOL" in type:
outputStrings.append("%i")
elif "NSUInteger" in type:
outputStrings.append("%u")
elif "id" in type:
outputStrings.append("%#")
elif "NSTimeInterval" in type:
outputStrings.append("%f")
elif "SEL" in type:
outputString.append("%s")
else:
outputStrings.append('"FIXME"')
if not var == vars[-1][1]:
outputStrings.append(',\\n"\n#" -')
outputStrings.append("\"")
for type, var in vars:
outputStrings.append(",\n")
outputStrings.append("[self %s]" % var)
outputStrings.append("];\n}")
print "".join(outputStrings),
Here's one that I found somewhere else that creates #property (copy) and #synthesize property directives for an instance variable. It could use a bit of improvement (say, to let you synthesize multiple variables at once), but it's better than creating them by hand.
Select the instance variable you want to create a property for and activate the script.
If I want a (retain) instead of (copy) I just activate the script and change it to retain manually (it's smart enough to not include the (copy) on primitive types such as int to begin with).
#! /usr/bin/perl -w
#Create property from instance variable
#Entire Document
#Home Directory
#Discard Output
#Display in Alert
use strict;
# Get the header file contents from Xcode user scripts
my $headerFileContents = <<'HEADERFILECONTENTS';
%%%{PBXAllText}%%%
HEADERFILECONTENTS
# Get the indices of the selection from Xcode user scripts
my $selectionStartIndex = %%%{PBXSelectionStart}%%%;
my $selectionEndIndex = %%%{PBXSelectionEnd}%%%;
# Get path of the header file
my $implementationFilePath = "%%%{PBXFilePath}%%%";
my $headerFilePath = $implementationFilePath;
# Look for an implemenation file with a ".m" or ".mm" extension
$implementationFilePath =~ s/\.[hm]*$/.m/;
if (!(-e $implementationFilePath))
{
$implementationFilePath =~ s/.m$/.mm/;
}
# Handle subroutine to trime whitespace off both ends of a string
sub trim
{
my $string = shift;
$string =~ s/^\s*(.*?)\s*$/$1/;
return $string;
}
# Get the selection out of the header file
my $selectedText = substr $headerFileContents, $selectionStartIndex, ($selectionEndIndex - $selectionStartIndex);
$selectedText = trim $selectedText;
my $type = "";
my $asterisk = "";
my $name = "";
my $behavior = "";
# Test that the selection is:
# At series of identifiers (the type name and access specifiers)
# Possibly an asterisk
# Another identifier (the variable name)
# A semi-colon
if (length($selectedText) && ($selectedText =~ /([_A-Za-z][_A-Za-z0-9]*\s*)+([\s\*]+)([_A-Za-z][_A-Za-z0-9]*);/))
{
$type = $1;
$type = trim $type;
$asterisk = $2;
$asterisk = trim $asterisk;
$name = $3;
$behavior = "";
if (defined($asterisk) && length($asterisk) == 1)
{
$behavior = "(copy) "; #"(nonatomic, retain) ";
}
else
{
$asterisk = "";
}
}
else
{
exit 1;
}
# Find the closing brace (end of the class variables section)
my $remainderOfHeader = substr $headerFileContents, $selectionEndIndex;
my $indexAfterClosingBrace = $selectionEndIndex + index($remainderOfHeader, "\n}\n") + 3;
if ($indexAfterClosingBrace == -1)
{
exit 1;
}
# Determine if we need to add a newline in front of the property declaration
my $leadingNewline = "\n";
if (substr($headerFileContents, $indexAfterClosingBrace, 1) eq "\n")
{
$indexAfterClosingBrace += 1;
$leadingNewline = "";
}
# Determine if we need to add a newline after the property declaration
my $trailingNewline = "\n";
if (substr($headerFileContents, $indexAfterClosingBrace, 9) eq "\#property")
{
$trailingNewline = "";
}
# Create and insert the propert declaration
my $propertyDeclaration = $leadingNewline . "\#property " . $behavior . $type . " " . $asterisk . $name . ";\n" . $trailingNewline;
substr($headerFileContents, $indexAfterClosingBrace, 0) = $propertyDeclaration;
my $replaceFileContentsScript = <<'REPLACEFILESCRIPT';
on run argv
set fileAlias to POSIX file (item 1 of argv)
set newDocText to (item 2 of argv)
tell application "Xcode"
set doc to open fileAlias
set text of doc to newDocText
end tell
end run
REPLACEFILESCRIPT
# Use Applescript to replace the contents of the header file
# (I could have used the "Output" of the Xcode user script instead)
system 'osascript', '-e', $replaceFileContentsScript, $headerFilePath, $headerFileContents;
# Stop now if the implementation file can't be found
if (!(-e $implementationFilePath))
{
exit 1;
}
my $getFileContentsScript = <<'GETFILESCRIPT';
on run argv
set fileAlias to POSIX file (item 1 of argv)
tell application "Xcode"
set doc to open fileAlias
set docText to text of doc
end tell
return docText
end run
GETFILESCRIPT
# Get the contents of the implmentation file
open(SCRIPTFILE, '-|') || exec 'osascript', '-e', $getFileContentsScript, $implementationFilePath;
my $implementationFileContents = do {local $/; <SCRIPTFILE>};
close(SCRIPTFILE);
# Look for the class implementation statement
if (length($implementationFileContents) && ($implementationFileContents =~ /(\#implementation [_A-Za-z][_A-Za-z0-9]*\n)/))
{
my $matchString = $1;
my $indexAfterMatch = index($implementationFileContents, $matchString) + length($matchString);
# Determine if we want a newline before the synthesize statement
$leadingNewline = "\n";
if (substr($implementationFileContents, $indexAfterMatch, 1) eq "\n")
{
$indexAfterMatch += 1;
$leadingNewline = "";
}
# Determine if we want a newline after the synthesize statement
$trailingNewline = "\n";
if (substr($implementationFileContents, $indexAfterMatch, 11) eq "\#synthesize")
{
$trailingNewline = "";
}
# Create and insert the synthesize statement
my $synthesizeStatement = $leadingNewline . "\#synthesize " . $name . ";\n" . $trailingNewline;
substr($implementationFileContents, $indexAfterMatch, 0) = $synthesizeStatement;
# Use Applescript to replace the contents of the implementation file in Xcode
system 'osascript', '-e', $replaceFileContentsScript, $implementationFilePath, $implementationFileContents;
}
exit 0;
This on creates #proptery, #synthesize, dealloc, viewDidUnload and public methods for you. Easy XCode integration:
http://github.com/holtwick/xobjc