├── .gitignore ├── LICENSE ├── README.md ├── bindata.go ├── data └── wrapper.js ├── phantom.go ├── phantom_example_test.go └── phantom_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Urturn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-phantomjs 2 | ============ 3 | 4 | A tiny phantomjs wrapper for go 5 | 6 | Usage 7 | ```go 8 | import ( 9 | "github.com/urturn/go-phantomjs" // exported package is phantomjs 10 | ) 11 | 12 | func main() { 13 | p, err := phantomjs.Start() 14 | if err != nil { 15 | panic(err) 16 | } 17 | defer p.Exit() // Don't forget to kill phantomjs at some point. 18 | var result interface{} 19 | err = p.Run("function() { return 2 + 2 }", &result) 20 | if err != nil { 21 | panic(err) 22 | } 23 | number, ok := result.(float64) 24 | if !ok { 25 | panic("Cannot convert result to float64") 26 | } 27 | fmt.Println(number) 28 | // Output: 4 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /bindata.go: -------------------------------------------------------------------------------- 1 | package phantomjs 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | func bindata_read(data []byte, name string) ([]byte, error) { 11 | gz, err := gzip.NewReader(bytes.NewBuffer(data)) 12 | if err != nil { 13 | return nil, fmt.Errorf("Read %q: %v", name, err) 14 | } 15 | 16 | var buf bytes.Buffer 17 | _, err = io.Copy(&buf, gz) 18 | gz.Close() 19 | 20 | if err != nil { 21 | return nil, fmt.Errorf("Read %q: %v", name, err) 22 | } 23 | 24 | return buf.Bytes(), nil 25 | } 26 | 27 | func data_wrapper_js() ([]byte, error) { 28 | return bindata_read([]byte{ 29 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x00, 0xff, 0x9c, 0x56, 30 | 0xdd, 0x52, 0xdb, 0xb8, 0x17, 0xbf, 0xcf, 0x53, 0x9c, 0x7f, 0x2e, 0x1a, 31 | 0x99, 0xb8, 0x0e, 0xfd, 0x5f, 0xc2, 0xa6, 0xdd, 0x8f, 0xa6, 0xbb, 0xe9, 32 | 0xb4, 0x69, 0xa7, 0x81, 0xdd, 0x0b, 0x42, 0xa7, 0xc2, 0x56, 0x12, 0x81, 33 | 0x2d, 0xbb, 0x92, 0x4c, 0x60, 0x98, 0xbc, 0xcb, 0x3e, 0xcb, 0x3e, 0xd9, 34 | 0x1e, 0x49, 0x96, 0xad, 0x00, 0x81, 0xce, 0xba, 0xd3, 0x60, 0x49, 0xe7, 35 | 0xfc, 0xce, 0xf7, 0x4f, 0x1e, 0x8d, 0xe0, 0xe4, 0x8f, 0xe9, 0x1c, 0xde, 36 | 0x4d, 0x3f, 0x4c, 0xe0, 0xe3, 0xe9, 0xfc, 0x04, 0x7e, 0x9d, 0xc0, 0x6f, 37 | 0x9f, 0x3e, 0x7e, 0xc6, 0xf5, 0x5b, 0x38, 0x9d, 0x4f, 0x67, 0xbf, 0xf7, 38 | 0x50, 0xe6, 0xdb, 0xaa, 0x7c, 0x79, 0xc1, 0x45, 0x46, 0x35, 0x85, 0x97, 39 | 0xd5, 0xd5, 0x6a, 0x5c, 0xad, 0xa9, 0xd0, 0x65, 0x71, 0xa9, 0xc0, 0xec, 40 | 0x7d, 0x33, 0x32, 0xba, 0x84, 0x0b, 0x06, 0x5c, 0xa4, 0x79, 0x9d, 0xb1, 41 | 0x0c, 0x5f, 0x40, 0xaf, 0x19, 0x54, 0xb2, 0xbc, 0x64, 0xa9, 0xee, 0xf5, 42 | 0xc8, 0xb2, 0x16, 0xa9, 0xe6, 0xa5, 0x20, 0x11, 0xdc, 0xf5, 0x00, 0xae, 43 | 0xa9, 0x04, 0x75, 0xab, 0x34, 0x2b, 0x60, 0x0c, 0x92, 0x7d, 0xaf, 0xb9, 44 | 0x64, 0x64, 0xe0, 0x76, 0x06, 0xd1, 0x71, 0x23, 0xc1, 0x33, 0x3c, 0x3d, 45 | 0x3c, 0xee, 0xe1, 0x72, 0x74, 0x70, 0x80, 0xbf, 0x70, 0x00, 0x27, 0x6b, 46 | 0xae, 0xa0, 0x60, 0x7a, 0x5d, 0x66, 0xa8, 0x48, 0x33, 0x50, 0x3a, 0x43, 47 | 0x6b, 0x54, 0x64, 0xc0, 0x6e, 0xb4, 0xa4, 0xa9, 0x86, 0xb4, 0x2c, 0x0a, 48 | 0xb3, 0x5e, 0xca, 0xb2, 0x00, 0xae, 0x13, 0xab, 0xe8, 0xb5, 0x99, 0x44, 49 | 0x37, 0x15, 0xe8, 0x4d, 0x09, 0x57, 0x18, 0x13, 0x94, 0x4b, 0x2f, 0xaf, 50 | 0x8e, 0x9c, 0xcc, 0xe4, 0xcf, 0x5f, 0x3e, 0x60, 0x3c, 0x57, 0x4c, 0x1c, 51 | 0x01, 0xbb, 0xa6, 0x79, 0x4d, 0x35, 0x03, 0x9a, 0xe7, 0x36, 0xa2, 0xba, 52 | 0x42, 0x69, 0x2e, 0x56, 0x90, 0x73, 0xc1, 0x14, 0x2e, 0x4d, 0xe4, 0xe6, 53 | 0x60, 0x32, 0x7b, 0xeb, 0x94, 0x9c, 0x39, 0xf8, 0x72, 0x3a, 0xf3, 0x20, 54 | 0xb2, 0x76, 0xd9, 0xf0, 0x39, 0x40, 0x83, 0x42, 0x53, 0xd4, 0x6f, 0xf3, 55 | 0xb4, 0x8b, 0x1a, 0xef, 0x87, 0x75, 0xd8, 0x73, 0x5a, 0x54, 0x39, 0x6b, 56 | 0xfd, 0x76, 0x9b, 0x2f, 0x77, 0x9f, 0x20, 0x5b, 0x3e, 0x1f, 0xec, 0x86, 57 | 0x6b, 0xe5, 0xb2, 0xf2, 0xd9, 0x55, 0xf0, 0xfd, 0xdc, 0x89, 0xbd, 0xb6, 58 | 0x41, 0xfb, 0xf7, 0xa6, 0xbc, 0x89, 0x91, 0x27, 0xb6, 0x18, 0x4e, 0x64, 59 | 0xf6, 0x76, 0x27, 0x93, 0x01, 0xb2, 0x64, 0xba, 0x96, 0x42, 0xc1, 0xff, 60 | 0xbd, 0x2c, 0x46, 0xef, 0x5f, 0xef, 0x55, 0xde, 0xee, 0xe1, 0xe3, 0x54, 61 | 0x3a, 0x8d, 0xed, 0x8f, 0xdb, 0xb1, 0x89, 0x51, 0x9a, 0xea, 0xda, 0x9c, 62 | 0x64, 0xcc, 0xd4, 0x90, 0x42, 0x45, 0x57, 0x0c, 0xf2, 0x92, 0x66, 0x4f, 63 | 0xf9, 0x90, 0x95, 0x82, 0xed, 0xf8, 0x61, 0xda, 0xcc, 0x6a, 0x06, 0x6d, 64 | 0x68, 0xd6, 0x83, 0xa8, 0x93, 0x31, 0xeb, 0xa4, 0xac, 0x98, 0x20, 0x83, 65 | 0xb5, 0xd6, 0xd5, 0xd1, 0x68, 0xb4, 0xd9, 0x6c, 0x30, 0x3b, 0xb6, 0x0a, 66 | 0x09, 0xfa, 0x36, 0x88, 0x3b, 0x0b, 0xce, 0xaf, 0xe8, 0x9f, 0xbf, 0xc3, 67 | 0x60, 0xc1, 0xd8, 0xf5, 0x47, 0xc7, 0xdd, 0xc1, 0xb6, 0x5b, 0x6c, 0xef, 68 | 0x87, 0x3f, 0xc2, 0xdf, 0xae, 0x63, 0x68, 0x85, 0x91, 0xb3, 0xa9, 0xa8, 69 | 0x6a, 0xed, 0x13, 0x69, 0x7d, 0x77, 0x6d, 0x38, 0x86, 0xb3, 0xf3, 0xe3, 70 | 0x6e, 0x0f, 0xd7, 0x6e, 0x94, 0x12, 0x3b, 0x1c, 0x89, 0x99, 0x93, 0x0f, 71 | 0x28, 0xd8, 0x14, 0x13, 0x36, 0x6b, 0x8e, 0xed, 0x43, 0x72, 0xf8, 0xdf, 72 | 0x78, 0x0c, 0x03, 0x34, 0x38, 0xf0, 0x90, 0xe0, 0x00, 0x93, 0xaa, 0x56, 73 | 0x6b, 0x92, 0x37, 0xe2, 0xf0, 0x3c, 0xe2, 0xb6, 0x35, 0xee, 0x4b, 0x35, 74 | 0x6e, 0x90, 0x54, 0x95, 0xf3, 0x94, 0x91, 0xc3, 0x18, 0x5e, 0x45, 0x67, 75 | 0x87, 0x8d, 0x97, 0x7c, 0x09, 0xa4, 0x15, 0xb4, 0x3e, 0x60, 0xfb, 0x05, 76 | 0x4e, 0x68, 0x79, 0xdb, 0xbe, 0x83, 0x1d, 0xc2, 0x24, 0xc5, 0x09, 0x24, 77 | 0x1a, 0x9b, 0x21, 0x6e, 0x80, 0x2f, 0x4b, 0x8e, 0x05, 0x59, 0x88, 0x41, 78 | 0xd4, 0xba, 0xb9, 0xc5, 0x3c, 0xe9, 0x74, 0x0d, 0x84, 0xdd, 0x44, 0x81, 79 | 0x7e, 0xe7, 0x79, 0x59, 0xeb, 0x64, 0x23, 0xb9, 0x66, 0xd6, 0xf7, 0xfe, 80 | 0x44, 0xca, 0x52, 0x42, 0x56, 0x4b, 0x33, 0x7a, 0x76, 0xec, 0xcb, 0x65, 81 | 0x1f, 0x86, 0x4f, 0x18, 0x68, 0xfe, 0x2a, 0xa6, 0x4f, 0x78, 0xc1, 0x10, 82 | 0x8e, 0x84, 0x95, 0x89, 0xe1, 0xd0, 0xe7, 0x03, 0x58, 0xae, 0xd8, 0xc3, 83 | 0x38, 0xb1, 0x27, 0x83, 0x30, 0x3d, 0xbb, 0x10, 0x9e, 0x0d, 0x87, 0x7b, 84 | 0xe3, 0x6a, 0xb0, 0xbc, 0xd2, 0xde, 0x68, 0xa6, 0x02, 0xe1, 0x90, 0x32, 85 | 0x1b, 0x83, 0x47, 0x3f, 0x99, 0x50, 0x9a, 0xc5, 0xb0, 0xff, 0xba, 0xdf, 86 | 0x46, 0xf1, 0xbc, 0xf7, 0x3d, 0xf3, 0x3f, 0x64, 0xdd, 0x89, 0xe7, 0x41, 87 | 0x33, 0x7a, 0x2b, 0x7e, 0xcd, 0x04, 0x52, 0x97, 0xd5, 0xa1, 0x38, 0x89, 88 | 0x36, 0x7f, 0x92, 0x55, 0x92, 0x29, 0x26, 0xb4, 0x59, 0xd0, 0xae, 0x75, 89 | 0x33, 0x96, 0xe6, 0x54, 0x52, 0xf3, 0x7e, 0x8f, 0x89, 0x51, 0x45, 0xd5, 90 | 0xb9, 0x06, 0xac, 0x01, 0xb3, 0x95, 0xc0, 0x39, 0xde, 0xe1, 0xc9, 0x0d, 91 | 0x47, 0xd6, 0xbd, 0xf0, 0x06, 0x91, 0x11, 0x03, 0x58, 0xbc, 0x5a, 0xb2, 92 | 0x3a, 0x65, 0xcd, 0xb4, 0x5f, 0xdc, 0xda, 0x11, 0x7b, 0x87, 0xa7, 0x98, 93 | 0xcc, 0x08, 0x95, 0xf2, 0x72, 0x13, 0x9a, 0xe3, 0xf7, 0xa0, 0xd7, 0x54, 94 | 0x59, 0xce, 0x90, 0x14, 0xef, 0x12, 0x26, 0x63, 0x30, 0xad, 0xd5, 0xad, 95 | 0x5b, 0xd3, 0x98, 0x2b, 0x4f, 0xc5, 0xa1, 0x01, 0x07, 0x5a, 0xb0, 0xe2, 96 | 0x82, 0xc9, 0x04, 0xa6, 0x4b, 0x10, 0xa5, 0x8e, 0xad, 0x94, 0xa3, 0x28, 97 | 0xa4, 0x76, 0x93, 0x31, 0xd6, 0xe2, 0x54, 0x54, 0x29, 0xdc, 0x44, 0xa8, 98 | 0x1d, 0x3f, 0x1b, 0x84, 0xc0, 0xd1, 0x9f, 0xad, 0x0f, 0x00, 0x77, 0x2e, 99 | 0xad, 0x5b, 0x73, 0x09, 0xe2, 0x53, 0x0b, 0xfe, 0x1d, 0xe1, 0x78, 0x66, 100 | 0x12, 0xbc, 0xe4, 0xe8, 0xe1, 0x12, 0x13, 0x66, 0x0c, 0xa6, 0xb5, 0x94, 101 | 0xb8, 0x69, 0x6e, 0x99, 0x7d, 0x08, 0xa6, 0x52, 0xa0, 0xca, 0x5a, 0xa6, 102 | 0x2c, 0x20, 0x4c, 0x9f, 0x8b, 0x87, 0x6c, 0x13, 0xf4, 0x65, 0xec, 0xb4, 103 | 0x43, 0xca, 0x31, 0x62, 0x58, 0x77, 0xb9, 0x52, 0xae, 0x5b, 0xf0, 0xf6, 104 | 0xdf, 0xdb, 0x93, 0xb3, 0xc9, 0x5f, 0xa6, 0x0b, 0x31, 0x86, 0x21, 0xf4, 105 | 0xf1, 0x1f, 0x34, 0x1d, 0x16, 0x0e, 0xb8, 0xb1, 0x46, 0xfa, 0x06, 0x15, 106 | 0x49, 0xc3, 0x4a, 0x5b, 0x8b, 0xbe, 0x5b, 0x8d, 0x21, 0x3c, 0xb0, 0xe7, 107 | 0x2f, 0x5e, 0x00, 0x09, 0xd6, 0x89, 0x2e, 0xe7, 0x36, 0x46, 0x12, 0x25, 108 | 0x85, 0x99, 0x7c, 0x32, 0xfa, 0xea, 0x83, 0x58, 0xa8, 0x21, 0x79, 0x73, 109 | 0xb4, 0xd8, 0x0c, 0xa3, 0x37, 0x0b, 0x42, 0xce, 0xbe, 0x2e, 0xa2, 0xf3, 110 | 0x83, 0x68, 0x11, 0x8d, 0x8a, 0xe8, 0xec, 0xd5, 0x39, 0xf2, 0x57, 0x95, 111 | 0x53, 0xa4, 0xa5, 0x11, 0x8a, 0x8d, 0x62, 0xe8, 0xf7, 0xa3, 0xc8, 0x80, 112 | 0x1b, 0x6c, 0xcb, 0x58, 0x9a, 0x0c, 0xe2, 0x41, 0xeb, 0x82, 0x19, 0x63, 113 | 0x73, 0x14, 0x92, 0x8a, 0xb1, 0x43, 0xda, 0x5a, 0xf6, 0xbf, 0x4c, 0xe6, 114 | 0xfd, 0x21, 0x56, 0x34, 0xe0, 0xa2, 0x9d, 0xa9, 0xbd, 0xcf, 0x69, 0x00, 115 | 0x0f, 0x75, 0xed, 0x47, 0x12, 0xe9, 0x20, 0xf6, 0x12, 0xda, 0xa3, 0xca, 116 | 0xa2, 0xce, 0xf3, 0x18, 0xef, 0xf8, 0x50, 0x7d, 0x87, 0xb6, 0x1e, 0x05, 117 | 0xfb, 0x0f, 0x5c, 0xf0, 0x85, 0x61, 0xce, 0xd9, 0x35, 0x7e, 0x13, 0xc1, 118 | 0x46, 0xd2, 0xaa, 0x62, 0xb2, 0xeb, 0x1c, 0xbd, 0xa6, 0xf8, 0xfd, 0x45, 119 | 0x45, 0xd0, 0xf2, 0xd4, 0x8f, 0xb9, 0x21, 0x41, 0x3c, 0xb1, 0xa3, 0xfe, 120 | 0x5c, 0xc7, 0x37, 0x7d, 0x7e, 0xbb, 0xd3, 0xe1, 0x0d, 0x97, 0x35, 0x5a, 121 | 0xcd, 0xb7, 0xc0, 0x9d, 0xb7, 0xbd, 0x85, 0x90, 0x1e, 0xac, 0x23, 0x34, 122 | 0x4d, 0x59, 0xa5, 0x1f, 0xf7, 0xa0, 0x9b, 0xf5, 0x87, 0xfd, 0xbf, 0x33, 123 | 0xa2, 0x2e, 0x53, 0x8d, 0xb5, 0x56, 0x84, 0x38, 0xc8, 0xd8, 0x80, 0x75, 124 | 0xd9, 0x34, 0x8d, 0xb2, 0xb3, 0x11, 0xd2, 0x35, 0x1e, 0x04, 0xa3, 0xd1, 125 | 0x8e, 0xc4, 0x10, 0xde, 0xcf, 0x3f, 0xcd, 0x12, 0x17, 0x3c, 0x86, 0xec, 126 | 0x00, 0xf0, 0x6c, 0x21, 0xfa, 0xfb, 0xbb, 0x69, 0xdf, 0xc4, 0xed, 0x87, 127 | 0x75, 0x0e, 0x3f, 0x44, 0xfe, 0xe1, 0x46, 0x38, 0xf6, 0x9d, 0xf0, 0xa4, 128 | 0xe8, 0xd6, 0x34, 0xf0, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x32, 0x6e, 129 | 0xce, 0x20, 0x59, 0x0c, 0x00, 0x00, 130 | }, 131 | "data/wrapper.js", 132 | ) 133 | } 134 | 135 | 136 | // Asset loads and returns the asset for the given name. 137 | // It returns an error if the asset could not be found or 138 | // could not be loaded. 139 | func Asset(name string) ([]byte, error) { 140 | if f, ok := _bindata[name]; ok { 141 | return f() 142 | } 143 | return nil, fmt.Errorf("Asset %s not found", name) 144 | } 145 | 146 | // _bindata is a table, holding each asset generator, mapped to its name. 147 | var _bindata = map[string] func() ([]byte, error) { 148 | "data/wrapper.js": data_wrapper_js, 149 | 150 | } 151 | -------------------------------------------------------------------------------- /data/wrapper.js: -------------------------------------------------------------------------------- 1 | // THIS FILE MUST BE COMPILED USING 2 | // `go-bindata -pkg=phantomjs data` 3 | // to be included in the project 4 | 5 | (function() { 6 | var system = require('system'); 7 | var id = 0; 8 | 9 | /** 10 | * This method read stdin and extract command from it. 11 | * 12 | * There is two kind of commands: 13 | * EVAL token: evaluate all the upcoming lines up to the END token. 14 | * RUN token: run the function contained in the upcoming lines, up to the END token. 15 | * 16 | * Sample commands 17 | * --------------- 18 | * This command exits from PhantomJS 19 | * > EVAL 20 | * > phantom.exit(); 21 | * > END 22 | * 23 | * This command returns 2 24 | * > RUN 25 | * > function() { 26 | * > return 2 27 | * > }; 28 | * > END 29 | * 30 | * This command return the status code of a page load 31 | * > RUN 32 | * > function(done) { 33 | * > var page = require('page') 34 | * > page.open('http://www.example.com', function(status) { 35 | * > done(status); 36 | * > }); 37 | * > } 38 | * > END 39 | */ 40 | function captureInput() { 41 | var lines = []; 42 | var l = system.stdin.readLine(); 43 | while (l !== 'END') { 44 | lines.push(l); 45 | l = system.stdin.readLine(); 46 | } 47 | var command = lines.splice(0, 1)[0]; 48 | if (command === 'EVAL') { 49 | try { 50 | eval.call(this, lines.join('\n')); 51 | } catch (ex) { 52 | system.stdout.writeLine("Error during EVAL of" + lines.join('\n')); 53 | } 54 | setTimeout(captureInput, 0); 55 | } else if (command === 'RUN') { 56 | evaluate(id++, lines.join('\n')); 57 | } else { 58 | system.stdout.writeLine("Invalid command:<" + command+">"); 59 | setTimeout(captureInput, 0); 60 | } 61 | } 62 | 63 | /** 64 | * Evaluate the given input, a string representing a function declaration. 65 | * 66 | * The result or error of the function will be given to a function produced 67 | * by doneFunc(id) below. 68 | * 69 | * if the function has a parameter, this parameter will be set to the doneFunc(id) 70 | * member. If not, the returned value will be passed to doneFunc(id) member. 71 | * 72 | * @param {string} id unique identifier for the current run 73 | * @param {string} input source code of a function 74 | */ 75 | function evaluate(id, input) { 76 | var func, args; 77 | // system.stdout.writeLine("NEW" + id + " " ); 78 | try { 79 | eval("func = " + input); 80 | args = func && (args = func.toString().match(/^function\s+(?:\w+)?\(([^\)]*)\)/m)[1].replace(/\s+/, "")) && args.split(','); 81 | if (args) { 82 | func(doneFunc("RES"+id)); 83 | } else { 84 | try { 85 | doneFunc("RES"+id)(func()); 86 | } catch (ex) { 87 | doneFunc("RES"+id)(null, ex); 88 | } 89 | } 90 | } catch (ex) { 91 | setTimeout(captureInput, 0); 92 | } 93 | } 94 | 95 | /** 96 | * Retrieve a wrapper function that can be passed a result and an error. 97 | * 98 | * @param {string} id identify the current command 99 | * @return {function} a function that accept a result and an error parameter 100 | */ 101 | function doneFunc(id) { 102 | return function (result, err) { 103 | if (err) { 104 | system.stderr.writeLine(id + " " + JSON.stringify(err) + "\n"); 105 | } else { 106 | system.stdout.writeLine(id + " " + JSON.stringify(result) + "\n"); 107 | } 108 | setTimeout(captureInput, 0); 109 | }; 110 | } 111 | 112 | setTimeout(captureInput, 0); 113 | }()); -------------------------------------------------------------------------------- /phantom.go: -------------------------------------------------------------------------------- 1 | package phantomjs 2 | 3 | import ( 4 | "bufio" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | "os/exec" 12 | "strings" 13 | ) 14 | 15 | type Phantom struct { 16 | cmd *exec.Cmd 17 | in io.WriteCloser 18 | out io.ReadCloser 19 | errout io.ReadCloser 20 | } 21 | 22 | var nbInstance = 0 23 | var wrapperFileName = "" 24 | 25 | /* 26 | Create a new `Phantomjs` instance and return it as a pointer. 27 | 28 | If an error occurs during command start, return it instead. 29 | */ 30 | func Start(args ...string) (*Phantom, error) { 31 | if nbInstance == 0 { 32 | wrapperFileName, _ = createWrapperFile() 33 | } 34 | nbInstance += 1 35 | args = append(args, wrapperFileName) 36 | cmd := exec.Command("phantomjs", args...) 37 | 38 | inPipe, err := cmd.StdinPipe() 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | outPipe, err := cmd.StdoutPipe() 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | errPipe, err := cmd.StderrPipe() 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | p := Phantom{ 54 | cmd: cmd, 55 | in: inPipe, 56 | out: outPipe, 57 | errout: errPipe, 58 | } 59 | err = cmd.Start() 60 | 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | return &p, nil 66 | } 67 | 68 | /* 69 | Exit Phantomjs by sending the "phantomjs.exit()" command 70 | and wait for the command to end. 71 | 72 | Return an error if one occured during exit command or if the program output a error value 73 | */ 74 | func (p *Phantom) Exit() error { 75 | err := p.Load("phantom.exit()") 76 | if err != nil { 77 | return err 78 | } 79 | 80 | err = p.cmd.Wait() 81 | if err != nil { 82 | return err 83 | } 84 | nbInstance -= 1 85 | if nbInstance == 0 { 86 | os.Remove(wrapperFileName) 87 | } 88 | 89 | return nil 90 | } 91 | 92 | /* 93 | Run the javascript function passed as a string and wait for the result. 94 | 95 | The result can be either in the return value of the function or the first argument passed 96 | to the function first arguments. 97 | */ 98 | func (p *Phantom) Run(jsFunc string, res *interface{}) error { 99 | err := p.sendLine("RUN", jsFunc, "END") 100 | if err != nil { 101 | return err 102 | } 103 | scannerOut := bufio.NewScanner(p.out) 104 | scannerErrorOut := bufio.NewScanner(p.errout) 105 | resMsg := make(chan string) 106 | errMsg := make(chan error) 107 | go func() { 108 | for scannerOut.Scan() { 109 | line := scannerOut.Text() 110 | parts := strings.SplitN(line, " ", 2) 111 | if strings.HasPrefix(line, "RES") { 112 | resMsg <- parts[1] 113 | close(resMsg) 114 | return 115 | } else { 116 | fmt.Printf("LOG %s\n", line) 117 | } 118 | } 119 | }() 120 | go func() { 121 | for scannerErrorOut.Scan() { 122 | line := scannerErrorOut.Text() 123 | parts := strings.SplitN(line, " ", 2) 124 | if strings.HasPrefix(line, "RES") { 125 | errMsg <- errors.New(parts[1]) 126 | close(errMsg) 127 | return 128 | } else { 129 | fmt.Printf("LOG %s\n", line) 130 | } 131 | } 132 | }() 133 | select { 134 | case text := <-resMsg: 135 | if res != nil { 136 | err = json.Unmarshal([]byte(text), res) 137 | if err != nil { 138 | return err 139 | } 140 | } 141 | return nil 142 | case err := <-errMsg: 143 | return err 144 | } 145 | } 146 | 147 | /* 148 | Eval `jsCode` in the main context. 149 | */ 150 | func (p *Phantom) Load(jsCode string) error { 151 | return p.sendLine("EVAL", jsCode, "END") 152 | } 153 | 154 | func createWrapperFile() (fileName string, err error) { 155 | wrapper, err := ioutil.TempFile("", "go-phantom-wrapper") 156 | if err != nil { 157 | return "", err 158 | } 159 | defer wrapper.Close() 160 | 161 | wrapperData, err := Asset("data/wrapper.js") 162 | if err != nil { 163 | return "", err 164 | } 165 | 166 | err = ioutil.WriteFile(wrapper.Name(), wrapperData, os.ModeType) 167 | if err != nil { 168 | return "", err 169 | } 170 | return wrapper.Name(), nil 171 | } 172 | 173 | func (p *Phantom) sendLine(lines ...string) error { 174 | for _, l := range lines { 175 | _, err := io.WriteString(p.in, l+"\n") 176 | if err != nil { 177 | return errors.New("Cannot Send: `" + l + "`") 178 | } 179 | } 180 | return nil 181 | } 182 | -------------------------------------------------------------------------------- /phantom_example_test.go: -------------------------------------------------------------------------------- 1 | package phantomjs 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleWithResult() { 8 | p, err := Start() 9 | if err != nil { 10 | panic(err) 11 | } 12 | defer p.Exit() // Don't forget to kill phantomjs at some point. 13 | var result interface{} 14 | err = p.Run("function() { return 2 + 2 }", &result) 15 | if err != nil { 16 | panic(err) 17 | } 18 | number, ok := result.(float64) 19 | if !ok { 20 | panic("Cannot convert result to float64") 21 | } 22 | fmt.Println(number) 23 | // Output: 4 24 | } 25 | 26 | func ExampleWithError() { 27 | p, err := Start() 28 | if err != nil { 29 | panic(err) 30 | } 31 | defer p.Exit() // Don't forget to kill phantomjs at some point. 32 | var result interface{} 33 | err = p.Run("function() { throw 'Ooops' }", &result) 34 | if err != nil { 35 | fmt.Println(err) 36 | } 37 | // Output: "Ooops" 38 | } 39 | -------------------------------------------------------------------------------- /phantom_test.go: -------------------------------------------------------------------------------- 1 | package phantomjs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestStartStop(t *testing.T) { 8 | p, err := Start() 9 | failOnError(err, t) 10 | err = p.Exit() 11 | failOnError(err, t) 12 | } 13 | 14 | func TestStartStopWithArgs(t *testing.T) { 15 | p, err := Start("--web-security=no") 16 | failOnError(err, t) 17 | err = p.Exit() 18 | failOnError(err, t) 19 | } 20 | 21 | func TestRunACommand(t *testing.T) { 22 | p, err := Start() 23 | defer p.Exit() 24 | failOnError(err, t) 25 | assertFloatResult("function(){ return 2 + 1; }\n", 3, p, t) 26 | } 27 | 28 | func TestRunACommandWithoutLineBreak(t *testing.T) { 29 | p, err := Start() 30 | defer p.Exit() 31 | failOnError(err, t) 32 | assertFloatResult("function(){ return 2 + 2; }", 4, p, t) 33 | } 34 | 35 | func TestRunAnAsyncCommand(t *testing.T) { 36 | p, err := Start() 37 | failOnError(err, t) 38 | defer p.Exit() 39 | assertFloatResult("function(done){ done(2 + 3) ; }\n", 5, p, t) 40 | p1, err := Start() 41 | failOnError(err, t) 42 | defer p1.Exit() 43 | assertFloatResult("function(done){ setTimeout(function() { done(3 + 3) ; }, 0); }\n", 6, p1, t) 44 | } 45 | 46 | func TestRunMultilineCommand(t *testing.T) { 47 | p, err := Start() 48 | failOnError(err, t) 49 | defer p.Exit() 50 | assertFloatResult("function() {\n\t return 3+4;\n}\n", 7, p, t) 51 | } 52 | 53 | func TestRunMultipleCommands(t *testing.T) { 54 | p, err := Start() 55 | failOnError(err, t) 56 | defer p.Exit() 57 | assertFloatResult("function() {return 1}", 1, p, t) 58 | assertFloatResult("function() {return 1}", 1, p, t) 59 | assertFloatResult("function() {return 1}", 1, p, t) 60 | } 61 | 62 | func TestLoadGlobal(t *testing.T) { 63 | p, err := Start() 64 | failOnError(err, t) 65 | defer p.Exit() 66 | p.Load("function result(result) { return result; }\nvar a = 2") 67 | assertFloatResult("function() {return result(a);}", 2, p, t) 68 | } 69 | 70 | func TestMessageSentAfterAnErrorDontCrash(t *testing.T) { 71 | p, err := Start() 72 | failOnError(err, t) 73 | defer p.Exit() 74 | p.Run("function(done) {done(null, 'manual'); done('should not panic');}", nil) 75 | } 76 | 77 | func TestDoubleErrorSendDontCrash(t *testing.T) { 78 | p, err := Start() 79 | failOnError(err, t) 80 | defer p.Exit() 81 | p.Run("function(done) {done(null, 'manual'); done(null, 'should not panic');}", nil) 82 | } 83 | 84 | func assertFloatResult(jsFunc string, expected float64, p *Phantom, t *testing.T) { 85 | var r interface{} 86 | err := p.Run(jsFunc, &r) 87 | failOnError(err, t) 88 | v, ok := r.(float64) 89 | if !ok { 90 | t.Errorf("Should be an int but is %v", r) 91 | return 92 | } 93 | if v != expected { 94 | t.Errorf("Should be %f but is %f", expected, v) 95 | } 96 | } 97 | 98 | func failOnError(err error, t *testing.T) { 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | } 103 | --------------------------------------------------------------------------------