Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d58fd6434f | ||
|
|
0f9733c890 | ||
|
|
14643d3cf4 | ||
|
|
d49f837b2e | ||
|
|
cb19d235c6 | ||
|
|
d41d1d8a34 | ||
|
|
b6d9c7816c | ||
|
|
7345d065b8 | ||
|
|
3211e8607d | ||
|
|
be3e0fa2c2 | ||
|
|
41d24fe134 | ||
|
|
e9f7141f65 | ||
|
|
d7e60e1e23 | ||
|
|
1ed6410380 | ||
|
|
42783f8886 | ||
|
|
80054e8aa9 | ||
|
|
7be4f10441 | ||
|
|
a0a8b57992 | ||
|
|
e8ee0862ef | ||
|
|
1b006b83ad | ||
|
|
71527eab2b | ||
|
|
1576c57241 | ||
|
|
187d602c91 | ||
|
|
e88cd2d880 | ||
|
|
4baea3e63f | ||
|
|
eeed7ad847 | ||
|
|
9f27939471 | ||
|
|
5e61291703 | ||
|
|
59174b40ff | ||
|
|
359c768041 | ||
|
|
a64861ce63 | ||
|
|
115d50f793 | ||
|
|
e4b5856338 | ||
|
|
a8cbfb1ec0 | ||
|
|
c4bb20fd12 | ||
|
|
5eba524bfc | ||
|
|
0ba46039a2 |
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
test-results/
|
||||
tmp/
|
||||
routes/
|
||||
/none
|
||||
/src
|
||||
pkg
|
||||
bin2
|
||||
@@ -14,5 +18,11 @@ app/tmp/main.go
|
||||
.settings
|
||||
.project
|
||||
public/config.codekit
|
||||
files
|
||||
files/
|
||||
/node_modules
|
||||
.idea
|
||||
*.iml
|
||||
target/
|
||||
package/leanote.tar.gz
|
||||
package/leanote/
|
||||
leanote.log
|
||||
24
.travis.yml
24
.travis.yml
@@ -1,29 +1,41 @@
|
||||
language: go
|
||||
go: 1.7
|
||||
go: 1.15
|
||||
|
||||
services:
|
||||
- mongodb # 2.4.12
|
||||
|
||||
install:
|
||||
- go version
|
||||
- export PATH=$PATH:$HOME/gopath/bin
|
||||
- go get -v github.com/leanote/leanote/app
|
||||
# - go get -v github.com/leanote/leanote/app
|
||||
- go get -u github.com/revel/cmd/revel
|
||||
- ls $GOPATH/src/github.com/revel/
|
||||
# - ls $GOPATH/src/github.com/revel/
|
||||
# - go get github.com/revel/moudle/revel
|
||||
# - go install github.com/revel/cmd/revel
|
||||
- revel version
|
||||
- pwd
|
||||
- ls
|
||||
|
||||
script:
|
||||
- wget https://github.com/leanote/leanote/archive/refs/heads/master.zip
|
||||
- unzip master.zip
|
||||
- mv leanote-master leanote
|
||||
- cd leanote
|
||||
|
||||
- mongo --version
|
||||
- mongorestore -h localhost -d leanote --dir ./mongodb_backup/leanote_install_data/
|
||||
|
||||
- cd ./sh
|
||||
# - cd $GOPATH/src/github.com/leanote/leanote/sh
|
||||
- sh run.sh &
|
||||
|
||||
# gen tmp/main.go, routes/routes.go
|
||||
- go run app/cmd/main.go
|
||||
#- go run app/cmd/main.go
|
||||
# build
|
||||
- go build -o leanote github.com/leanote/leanote/app/tmp
|
||||
#- go build -o leanote github.com/leanote/leanote/app/tmp
|
||||
# run with port 9000
|
||||
- ./leanote -importPath=github.com/leanote/leanote -runMode=dev -port=9000 &
|
||||
#- ./leanote -importPath=github.com/leanote/leanote -runMode=dev -port=9000 &
|
||||
|
||||
- sleep 10s;
|
||||
# test
|
||||
- curl http://localhost:9000
|
||||
|
||||
359
LICENSE
359
LICENSE
@@ -1,359 +0,0 @@
|
||||
LEANOTE - NOT JUST A NOTEPAD!
|
||||
|
||||
Copyright 2014-2015 by the contributors
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
Leanote is licensed under the GPL v2.
|
||||
|
||||
life(life@leanote.com, lifephp@gmail.com)
|
||||
|
||||
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
@@ -50,7 +50,7 @@ More information about how to install Leanote please see:
|
||||
* [Windows](https://github.com/leanote/leanote/wiki/leanote-source-installation-on-Windows-(En))
|
||||
* [Mac and Linux](https://github.com/leanote/leanote/wiki/leanote-binary-installation-on-Mac-and-Linux-(En))
|
||||
* Leanote source installation tutorial:
|
||||
* [Windows](https://github.com/leanote/leanote/wiki/leanote-source-installation-on-Windows-(En))
|
||||
<!-- * [Windows](https://github.com/leanote/leanote/wiki/leanote-source-installation-on-Windows-(En)) -->
|
||||
* [Mac and Linux](https://github.com/leanote/leanote/wiki/Leanote-source-installation-on-Mac-and-Linux-(En))
|
||||
|
||||
## 4. Documentation
|
||||
@@ -86,7 +86,7 @@ We acknowledge the donations made by all the [donators](http://leanote.leanote.c
|
||||
## 9. Related projects
|
||||
|
||||
* [Leanote Desktop App](https://github.com/leanote/desktop-app), [Download](http://app.leanote.com)
|
||||
* [Leanote iOS](https://github.com/leanote/leanote-ios), [Download From App Store](https://itunes.apple.com/en/app/leanote/id1022302858?mt=8)
|
||||
* [Leanote iOS](https://github.com/leanote/leanote-ios), [Download From App Store](https://itunes.apple.com/app/leanote/id1022302858)
|
||||
* [Leanote Android](https://github.com/leanote/leanote-android), development phase
|
||||
|
||||
You are welcome to join us.
|
||||
@@ -145,7 +145,7 @@ Leanote云笔记产品包括: Leanote Web & Server(即本仓库), 桌面客户
|
||||
* [Windows](https://github.com/leanote/leanote/wiki/Leanote%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%89%88%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B---Windows)
|
||||
* [Mac, Linux](https://github.com/leanote/leanote/wiki/leanote%E4%BA%8C%E8%BF%9B%E5%88%B6%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B)
|
||||
* Leanote源码详细安装教程:
|
||||
* [Windows](https://github.com/leanote/leanote/wiki/Leanote-%E6%BA%90%E7%A0%81%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B----Windows)
|
||||
<!-- * [Windows](https://github.com/leanote/leanote/wiki/Leanote-%E6%BA%90%E7%A0%81%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B----Windows) -->
|
||||
* [Mac, Linux](https://github.com/leanote/leanote/wiki/leanote%E5%BC%80%E5%8F%91%E7%89%88%E8%AF%A6%E7%BB%86%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B)
|
||||
|
||||
## 4. 相关文档
|
||||
|
||||
26
Vendor.md
26
Vendor.md
@@ -1,26 +0,0 @@
|
||||
# Vendor
|
||||
|
||||
脱离 revel 每次升级不向下兼容 的控制!! 基于revel 0.18
|
||||
|
||||
## Install leanote_revel cmd
|
||||
|
||||
```
|
||||
go install github.com/leanote/leanote/cmd/leanote_revel
|
||||
leanote_revel run github.com/leanote/leanote
|
||||
````
|
||||
|
||||
## build leanote
|
||||
|
||||
在当前目录生成了leanote二进制文件
|
||||
|
||||
```
|
||||
go build -o ./leanote github.com/leanote/leanote/app/tmp
|
||||
```
|
||||
|
||||
## 运行leanote
|
||||
|
||||
其中-importPath是必须的
|
||||
|
||||
```
|
||||
./leanote -importPath=github.com/leanote/leanote -runMode=prod -port=9000
|
||||
```
|
||||
9
app/cmd/README.md
Normal file
9
app/cmd/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
全部代码来自https://github.com/revel/cmd
|
||||
|
||||
因为要改parse2, 所以改只要一点点代码
|
||||
harness/
|
||||
build.go 只要gensource, 其它的先return
|
||||
main.go 改动很小
|
||||
build.go 改动很小
|
||||
parser2/
|
||||
source_processors.go 改了 fsWalk 过滤掉 public, files, build 等文件夹
|
||||
270
app/cmd/build.go
Normal file
270
app/cmd/build.go
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"fmt"
|
||||
"github.com/leanote/leanote/app/cmd/harness" // 只改了这个
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
)
|
||||
|
||||
var cmdBuild = &Command{
|
||||
UsageLine: "revel build [-r [run mode]] [import path] [target path] ",
|
||||
Short: "build a Revel application (e.g. for deployment)",
|
||||
Long: `
|
||||
Build the Revel web application named by the given import path.
|
||||
This allows it to be deployed and run on a machine that lacks a Go installation.
|
||||
|
||||
For example:
|
||||
|
||||
revel build github.com/revel/examples/chat /tmp/chat
|
||||
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdBuild.RunWith = buildApp
|
||||
cmdBuild.UpdateConfig = updateBuildConfig
|
||||
}
|
||||
|
||||
// The update config updates the configuration command so that it can run
|
||||
func updateBuildConfig(c *model.CommandConfig, args []string) bool {
|
||||
c.Index = model.BUILD
|
||||
if c.Build.TargetPath == "" {
|
||||
c.Build.TargetPath = "target"
|
||||
}
|
||||
if len(args) == 0 && c.Build.ImportPath != "" {
|
||||
return true
|
||||
}
|
||||
// If arguments were passed in then there must be two
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
||||
return false
|
||||
}
|
||||
|
||||
c.Build.ImportPath = args[0]
|
||||
c.Build.TargetPath = args[1]
|
||||
if len(args) > 2 {
|
||||
c.Build.Mode = args[2]
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// The main entry point to build application from command line
|
||||
func buildApp(c *model.CommandConfig) (err error) {
|
||||
|
||||
appImportPath, destPath, mode := c.ImportPath, c.Build.TargetPath, DefaultRunMode
|
||||
if len(c.Build.Mode) > 0 {
|
||||
mode = c.Build.Mode
|
||||
}
|
||||
|
||||
// Convert target to absolute path
|
||||
c.Build.TargetPath, _ = filepath.Abs(destPath)
|
||||
c.Build.Mode = mode
|
||||
c.Build.ImportPath = appImportPath
|
||||
|
||||
revel_paths, err := model.NewRevelPaths(mode, appImportPath, c.AppPath, model.NewWrappedRevelCallback(nil, c.PackageResolver))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = buildSafetyCheck(destPath); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Ensure the application can be built, this generates the main file
|
||||
app, err := harness.Build(c, revel_paths)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Copy files
|
||||
// Included are:
|
||||
// - run scripts
|
||||
// - binary
|
||||
// - revel
|
||||
// - app
|
||||
|
||||
return // 改了这里
|
||||
|
||||
packageFolders, err := buildCopyFiles(c, app, revel_paths)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = buildCopyModules(c, revel_paths, packageFolders, app)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = buildWriteScripts(c, app)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Copy the files to the target
|
||||
func buildCopyFiles(c *model.CommandConfig, app *harness.App, revel_paths *model.RevelContainer) (packageFolders []string, err error) {
|
||||
appImportPath, destPath := c.ImportPath, c.Build.TargetPath
|
||||
|
||||
// Revel and the app are in a directory structure mirroring import path
|
||||
srcPath := filepath.Join(destPath, "src")
|
||||
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
||||
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(model.RevelImportPath))
|
||||
if err = utils.CopyFile(destBinaryPath, filepath.Join(revel_paths.BasePath, app.BinaryPath)); err != nil {
|
||||
return
|
||||
}
|
||||
utils.MustChmod(destBinaryPath, 0755)
|
||||
|
||||
// Copy the templates from the revel
|
||||
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel_paths.RevelPath, "conf"), nil); err != nil {
|
||||
return
|
||||
}
|
||||
if err = utils.CopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel_paths.RevelPath, "templates"), nil); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the folders to be packaged
|
||||
packageFolders = strings.Split(revel_paths.Config.StringDefault("package.folders", "conf,public,app/views"), ",")
|
||||
for i, p := range packageFolders {
|
||||
// Clean spaces, reformat slash to filesystem
|
||||
packageFolders[i] = filepath.FromSlash(strings.TrimSpace(p))
|
||||
}
|
||||
|
||||
if c.Build.CopySource {
|
||||
err = utils.CopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel_paths.BasePath, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for _, folder := range packageFolders {
|
||||
err = utils.CopyDir(
|
||||
filepath.Join(srcPath, filepath.FromSlash(appImportPath), folder),
|
||||
filepath.Join(revel_paths.BasePath, folder),
|
||||
nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Based on the section copy over the build modules
|
||||
func buildCopyModules(c *model.CommandConfig, revel_paths *model.RevelContainer, packageFolders []string, app *harness.App) (err error) {
|
||||
destPath := filepath.Join(c.Build.TargetPath, "src")
|
||||
// Find all the modules used and copy them over.
|
||||
config := revel_paths.Config.Raw()
|
||||
|
||||
// We should only copy over the section of options what the build is targeted for
|
||||
// We will default to prod
|
||||
moduleImportList := []string{}
|
||||
for _, section := range config.Sections() {
|
||||
// If the runmode is defined we will only import modules defined for that run mode
|
||||
if c.Build.Mode != "" && c.Build.Mode != section {
|
||||
continue
|
||||
}
|
||||
options, _ := config.SectionOptions(section)
|
||||
for _, key := range options {
|
||||
if !strings.HasPrefix(key, "module.") {
|
||||
continue
|
||||
}
|
||||
moduleImportPath, _ := config.String(section, key)
|
||||
if moduleImportPath == "" {
|
||||
continue
|
||||
}
|
||||
moduleImportList = append(moduleImportList, moduleImportPath)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the the paths for each of the modules
|
||||
for _, importPath := range moduleImportList {
|
||||
fsPath := app.PackagePathMap[importPath]
|
||||
utils.Logger.Info("Copy files ", "to", filepath.Join(destPath, importPath), "from", fsPath)
|
||||
if c.Build.CopySource {
|
||||
err = utils.CopyDir(filepath.Join(destPath, importPath), fsPath, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
for _, folder := range packageFolders {
|
||||
err = utils.CopyDir(
|
||||
filepath.Join(destPath, importPath, folder),
|
||||
filepath.Join(fsPath, folder),
|
||||
nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Write the run scripts for the build
|
||||
func buildWriteScripts(c *model.CommandConfig, app *harness.App) (err error) {
|
||||
tmplData := map[string]interface{}{
|
||||
"BinName": filepath.Base(app.BinaryPath),
|
||||
"ImportPath": c.Build.ImportPath,
|
||||
"Mode": c.Build.Mode,
|
||||
}
|
||||
|
||||
err = utils.GenerateTemplate(
|
||||
filepath.Join(c.Build.TargetPath, "run.sh"),
|
||||
PACKAGE_RUN_SH,
|
||||
tmplData,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
utils.MustChmod(filepath.Join(c.Build.TargetPath, "run.sh"), 0755)
|
||||
err = utils.GenerateTemplate(
|
||||
filepath.Join(c.Build.TargetPath, "run.bat"),
|
||||
PACKAGE_RUN_BAT,
|
||||
tmplData,
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Your application has been built in:", c.Build.TargetPath)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Checks to see if the target folder exists and can be created
|
||||
func buildSafetyCheck(destPath string) error {
|
||||
|
||||
// First, verify that it is either already empty or looks like a previous
|
||||
// build (to avoid clobbering anything)
|
||||
if utils.Exists(destPath) && !utils.Empty(destPath) && !utils.Exists(filepath.Join(destPath, "run.sh")) {
|
||||
return utils.NewBuildError("Abort: %s exists and does not look like a build directory.", "path", destPath)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
||||
return utils.NewBuildIfError(err, "Remove all error", "path", destPath)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(destPath, 0777); err != nil {
|
||||
return utils.NewBuildIfError(err, "MkDir all error", "path", destPath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const PACKAGE_RUN_SH = `#!/bin/sh
|
||||
|
||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
|
||||
`
|
||||
const PACKAGE_RUN_BAT = `@echo off
|
||||
|
||||
{{.BinName}} -importPath {{.ImportPath}} -srcPath "%CD%\src" -runMode {{.Mode}}
|
||||
`
|
||||
4
app/cmd/gen_tmp.sh
Normal file
4
app/cmd/gen_tmp.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||
cd $SCRIPTPATH
|
||||
go run . build -v ../../ ./tmptmp
|
||||
rm -rf ./tmptmp
|
||||
219
app/cmd/harness/app.go
Normal file
219
app/cmd/harness/app.go
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// App contains the configuration for running a Revel app. (Not for the app itself)
|
||||
// Its only purpose is constructing the command to execute.
|
||||
type App struct {
|
||||
BinaryPath string // Path to the app executable
|
||||
Port int // Port to pass as a command line argument.
|
||||
cmd AppCmd // The last cmd returned.
|
||||
PackagePathMap map[string]string // Package to directory path map
|
||||
Paths *model.RevelContainer
|
||||
}
|
||||
|
||||
// NewApp returns app instance with binary path in it
|
||||
func NewApp(binPath string, paths *model.RevelContainer, packagePathMap map[string]string) *App {
|
||||
return &App{BinaryPath: binPath, Paths: paths, Port: paths.HTTPPort, PackagePathMap:packagePathMap}
|
||||
}
|
||||
|
||||
// Cmd returns a command to run the app server using the current configuration.
|
||||
func (a *App) Cmd(runMode string) AppCmd {
|
||||
a.cmd = NewAppCmd(a.BinaryPath, a.Port, runMode, a.Paths)
|
||||
return a.cmd
|
||||
}
|
||||
|
||||
// Kill the last app command returned.
|
||||
func (a *App) Kill() {
|
||||
a.cmd.Kill()
|
||||
}
|
||||
|
||||
// AppCmd manages the running of a Revel app server.
|
||||
// It requires revel.Init to have been called previously.
|
||||
type AppCmd struct {
|
||||
*exec.Cmd
|
||||
}
|
||||
|
||||
// NewAppCmd returns the AppCmd with parameters initialized for running app
|
||||
func NewAppCmd(binPath string, port int, runMode string, paths *model.RevelContainer) AppCmd {
|
||||
cmd := exec.Command(binPath,
|
||||
fmt.Sprintf("-port=%d", port),
|
||||
fmt.Sprintf("-importPath=%s", paths.ImportPath),
|
||||
fmt.Sprintf("-runMode=%s", runMode))
|
||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||
return AppCmd{cmd}
|
||||
}
|
||||
|
||||
// Start the app server, and wait until it is ready to serve requests.
|
||||
func (cmd AppCmd) Start(c *model.CommandConfig) error {
|
||||
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool), c, &bytes.Buffer{}}
|
||||
cmd.Stdout = listeningWriter
|
||||
utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath)
|
||||
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args, "dir", cmd.Dir, "env", cmd.Env)
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
utils.Logger.Fatal("Error running:", "error", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case exitState := <-cmd.waitChan():
|
||||
fmt.Println("Startup failure view previous messages, \n Proxy is listening :", c.Run.Port)
|
||||
err := utils.NewError("", "Revel Run Error", "starting your application there was an exception. See terminal output, " + exitState, "")
|
||||
// TODO pretiffy command line output
|
||||
// err.MetaError = listeningWriter.getLastOutput()
|
||||
return err
|
||||
|
||||
case <-time.After(60 * time.Second):
|
||||
println("Revel proxy is listening, point your browser to :", c.Run.Port)
|
||||
utils.Logger.Error("Killing revel server process did not respond after wait timeout.", "processid", cmd.Process.Pid)
|
||||
cmd.Kill()
|
||||
return errors.New("revel/harness: app timed out")
|
||||
|
||||
case <-listeningWriter.notifyReady:
|
||||
println("Revel proxy is listening, point your browser to :", c.Run.Port)
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Run the app server inline. Never returns.
|
||||
func (cmd AppCmd) Run(c *model.CommandConfig) {
|
||||
utils.CmdInit(cmd.Cmd, !c.Vendored, c.AppPath)
|
||||
utils.Logger.Info("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||
if err := cmd.Cmd.Run(); err != nil {
|
||||
utils.Logger.Fatal("Error running:", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Kill terminates the app server if it's running.
|
||||
func (cmd AppCmd) Kill() {
|
||||
|
||||
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
|
||||
// Windows appears to send the kill to all threads, shutting down the
|
||||
// server before this can, this check will ensure the process is still running
|
||||
if _, err := os.FindProcess(int(cmd.Process.Pid)); err != nil {
|
||||
// Server has already exited
|
||||
utils.Logger.Info("Server not running revel server pid", "pid", cmd.Process.Pid)
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for the shutdown channel
|
||||
waitMutex := &sync.WaitGroup{}
|
||||
waitMutex.Add(1)
|
||||
ch := make(chan bool, 1)
|
||||
go func() {
|
||||
waitMutex.Done()
|
||||
s, err := cmd.Process.Wait()
|
||||
defer func() {
|
||||
ch <- true
|
||||
}()
|
||||
if err != nil {
|
||||
utils.Logger.Info("Wait failed for process ", "error", err)
|
||||
}
|
||||
if s != nil {
|
||||
utils.Logger.Info("Revel App exited", "state", s.String())
|
||||
}
|
||||
}()
|
||||
// Wait for the channel to begin waiting
|
||||
waitMutex.Wait()
|
||||
|
||||
// Send an interrupt signal to allow for a graceful shutdown
|
||||
utils.Logger.Info("Killing revel server pid", "pid", cmd.Process.Pid)
|
||||
var err error
|
||||
if runtime.GOOS != "windows" {
|
||||
// os.Interrupt is not available on windows
|
||||
err = cmd.Process.Signal(os.Interrupt)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.Logger.Info(
|
||||
"Revel app already exited.",
|
||||
"processid", cmd.Process.Pid, "error", err,
|
||||
"killerror", cmd.Process.Kill())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// Use a timer to ensure that the process exits
|
||||
utils.Logger.Info("Waiting to exit")
|
||||
select {
|
||||
case <-ch:
|
||||
return
|
||||
case <-time.After(60 * time.Second):
|
||||
// Kill the process
|
||||
utils.Logger.Error(
|
||||
"Revel app failed to exit in 60 seconds - killing.",
|
||||
"processid", cmd.Process.Pid,
|
||||
"killerror", cmd.Process.Kill())
|
||||
}
|
||||
|
||||
utils.Logger.Info("Done Waiting to exit")
|
||||
}
|
||||
}
|
||||
|
||||
// Return a channel that is notified when Wait() returns.
|
||||
func (cmd AppCmd) waitChan() <-chan string {
|
||||
ch := make(chan string, 1)
|
||||
go func() {
|
||||
_ = cmd.Wait()
|
||||
state := cmd.ProcessState
|
||||
exitStatus := " unknown "
|
||||
if state != nil {
|
||||
exitStatus = state.String()
|
||||
}
|
||||
|
||||
ch <- exitStatus
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// A io.Writer that copies to the destination, and listens for "Revel engine is listening on.."
|
||||
// in the stream. (Which tells us when the revel server has finished starting up)
|
||||
// This is super ghetto, but by far the simplest thing that should work.
|
||||
type startupListeningWriter struct {
|
||||
dest io.Writer
|
||||
notifyReady chan bool
|
||||
c *model.CommandConfig
|
||||
buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
// Writes to this output stream
|
||||
func (w *startupListeningWriter) Write(p []byte) (int, error) {
|
||||
if w.notifyReady != nil && bytes.Contains(p, []byte("Revel engine is listening on")) {
|
||||
w.notifyReady <- true
|
||||
w.notifyReady = nil
|
||||
}
|
||||
if w.c.HistoricMode {
|
||||
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening on")) {
|
||||
w.notifyReady <- true
|
||||
w.notifyReady = nil
|
||||
}
|
||||
}
|
||||
if w.notifyReady != nil {
|
||||
w.buffer.Write(p)
|
||||
}
|
||||
return w.dest.Write(p)
|
||||
}
|
||||
|
||||
// Returns the cleaned output from the response
|
||||
// TODO clean the response more
|
||||
func (w *startupListeningWriter) getLastOutput() string {
|
||||
return w.buffer.String()
|
||||
}
|
||||
|
||||
566
app/cmd/harness/build.go
Normal file
566
app/cmd/harness/build.go
Normal file
@@ -0,0 +1,566 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/leanote/leanote/app/cmd/parser2"
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/parser"
|
||||
_ "github.com/revel/cmd/parser"
|
||||
"github.com/revel/cmd/utils"
|
||||
)
|
||||
|
||||
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
|
||||
|
||||
type ByString []*model.TypeInfo
|
||||
|
||||
func (c ByString) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
func (c ByString) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
func (c ByString) Less(i, j int) bool {
|
||||
return c[i].String() < c[j].String()
|
||||
}
|
||||
|
||||
// Build the app:
|
||||
// 1. Generate the the main.go file.
|
||||
// 2. Run the appropriate "go build" command.
|
||||
// Requires that revel.Init has been called previously.
|
||||
// Returns the path to the built binary, and an error if there was a problem building it.
|
||||
func Build(c *model.CommandConfig, paths *model.RevelContainer) (_ *App, err error) {
|
||||
// First, clear the generated files (to avoid them messing with ProcessSource).
|
||||
cleanSource(paths, "tmp", "routes")
|
||||
|
||||
var sourceInfo *model.SourceInfo
|
||||
|
||||
if c.HistoricBuildMode {
|
||||
sourceInfo, err = parser.ProcessSource(paths)
|
||||
} else {
|
||||
sourceInfo, err = parser2.ProcessSource(paths)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Add the db.import to the import paths.
|
||||
if dbImportPath, found := paths.Config.String("db.import"); found {
|
||||
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, strings.Split(dbImportPath, ",")...)
|
||||
}
|
||||
|
||||
// Sort controllers so that file generation is reproducible
|
||||
controllers := sourceInfo.ControllerSpecs()
|
||||
sort.Stable(ByString(controllers))
|
||||
|
||||
// Generate two source files.
|
||||
templateArgs := map[string]interface{}{
|
||||
"ImportPath": paths.ImportPath,
|
||||
"Controllers": controllers,
|
||||
"ValidationKeys": sourceInfo.ValidationKeys,
|
||||
"ImportPaths": calcImportAliases(sourceInfo),
|
||||
"TestSuites": sourceInfo.TestSuites(),
|
||||
}
|
||||
|
||||
// Generate code for the main, run and routes file.
|
||||
// The run file allows external programs to launch and run the application
|
||||
// without being the main thread
|
||||
cleanSource(paths, "tmp", "routes")
|
||||
|
||||
if err = genSource(paths, "tmp", "main.go", RevelMainTemplate, templateArgs); err != nil {
|
||||
return
|
||||
}
|
||||
if err = genSource(paths, filepath.Join("tmp", "run"), "run.go", RevelRunTemplate, templateArgs); err != nil {
|
||||
return
|
||||
}
|
||||
if err = genSource(paths, "routes", "routes.go", RevelRoutesTemplate, templateArgs); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Logger.Warn("gen tmp/main.go, tmp/run/run.go, routes/routes.go success!!")
|
||||
|
||||
return // 改了这里
|
||||
|
||||
// Read build config.
|
||||
buildTags := paths.Config.StringDefault("build.tags", "")
|
||||
|
||||
// Build the user program (all code under app).
|
||||
// It relies on the user having "go" installed.
|
||||
goPath, err := exec.LookPath("go")
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Go executable not found in PATH.")
|
||||
}
|
||||
|
||||
// Binary path is a combination of target/app directory, app's import path and its name.
|
||||
binName := filepath.Join("target", "app", paths.ImportPath, filepath.Base(paths.BasePath))
|
||||
|
||||
// Change binary path for Windows build
|
||||
goos := runtime.GOOS
|
||||
if goosEnv := os.Getenv("GOOS"); goosEnv != "" {
|
||||
goos = goosEnv
|
||||
}
|
||||
if goos == "windows" {
|
||||
binName += ".exe"
|
||||
}
|
||||
|
||||
gotten := make(map[string]struct{})
|
||||
contains := func(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if len(c.GoModFlags) > 0 {
|
||||
for _, gomod := range c.GoModFlags {
|
||||
goModCmd := exec.Command(goPath, append([]string{"mod"}, strings.Split(gomod, " ")...)...)
|
||||
utils.CmdInit(goModCmd, !c.Vendored, c.AppPath)
|
||||
output, err := goModCmd.CombinedOutput()
|
||||
utils.Logger.Info("Gomod applied ", "output", string(output))
|
||||
|
||||
// If the build succeeded, we're done.
|
||||
if err != nil {
|
||||
utils.Logger.Error("Gomod Failed continuing ", "error", err, "output", string(output))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
appVersion := getAppVersion(paths)
|
||||
if appVersion == "" {
|
||||
appVersion = "noVersionProvided"
|
||||
}
|
||||
|
||||
buildTime := time.Now().UTC().Format(time.RFC3339)
|
||||
versionLinkerFlags := fmt.Sprintf("-X '%s/app.AppVersion=%s' -X '%s/app.BuildTime=%s'",
|
||||
paths.ImportPath, appVersion, paths.ImportPath, buildTime)
|
||||
|
||||
// Append any build flags specified, they will override existing flags
|
||||
flags := []string{}
|
||||
if len(c.BuildFlags) == 0 {
|
||||
flags = []string{
|
||||
"build",
|
||||
"-ldflags", versionLinkerFlags,
|
||||
"-tags", buildTags,
|
||||
"-o", binName}
|
||||
} else {
|
||||
if !contains(c.BuildFlags, "build") {
|
||||
flags = []string{"build"}
|
||||
}
|
||||
if !contains(flags, "-ldflags") {
|
||||
ldflags := "-ldflags= " + versionLinkerFlags
|
||||
// Add user defined build flags
|
||||
for i := range c.BuildFlags {
|
||||
ldflags += " -X '" + c.BuildFlags[i] + "'"
|
||||
}
|
||||
flags = append(flags, ldflags)
|
||||
}
|
||||
if !contains(flags, "-tags") && buildTags != "" {
|
||||
flags = append(flags, "-tags", buildTags)
|
||||
}
|
||||
if !contains(flags, "-o") {
|
||||
flags = append(flags, "-o", binName)
|
||||
}
|
||||
}
|
||||
|
||||
// Note: It's not applicable for filepath.* usage
|
||||
flags = append(flags, path.Join(paths.ImportPath, "app", "tmp"))
|
||||
|
||||
buildCmd := exec.Command(goPath, flags...)
|
||||
if !c.Vendored {
|
||||
// This is Go main path
|
||||
gopath := c.GoPath
|
||||
for _, o := range paths.ModulePathMap {
|
||||
gopath += string(filepath.ListSeparator) + o.Path
|
||||
}
|
||||
|
||||
buildCmd.Env = append(os.Environ(),
|
||||
"GOPATH=" + gopath,
|
||||
)
|
||||
}
|
||||
utils.CmdInit(buildCmd, !c.Vendored, c.AppPath)
|
||||
|
||||
utils.Logger.Info("Exec:", "args", buildCmd.Args, "working dir", buildCmd.Dir)
|
||||
output, err := buildCmd.CombinedOutput()
|
||||
|
||||
// If the build succeeded, we're done.
|
||||
if err == nil {
|
||||
utils.Logger.Info("Build successful continuing")
|
||||
return NewApp(binName, paths, sourceInfo.PackageMap), nil
|
||||
}
|
||||
|
||||
// Since there was an error, capture the output in case we need to report it
|
||||
stOutput := string(output)
|
||||
utils.Logger.Infof("Got error on build of app %s", stOutput)
|
||||
|
||||
// See if it was an import error that we can go get.
|
||||
matches := importErrorPattern.FindAllStringSubmatch(stOutput, -1)
|
||||
utils.Logger.Info("Build failed checking for missing imports", "message", stOutput, "missing_imports", len(matches))
|
||||
if matches == nil {
|
||||
utils.Logger.Info("Build failed no missing imports", "message", stOutput)
|
||||
return nil, newCompileError(paths, output)
|
||||
}
|
||||
utils.Logger.Warn("Detected missing packages, importing them", "packages", len(matches))
|
||||
for _, match := range matches {
|
||||
// Ensure we haven't already tried to go get it.
|
||||
pkgName := match[1]
|
||||
utils.Logger.Info("Trying to import ", "package", pkgName)
|
||||
if _, alreadyTried := gotten[pkgName]; alreadyTried {
|
||||
utils.Logger.Error("Failed to import ", "package", pkgName)
|
||||
return nil, newCompileError(paths, output)
|
||||
}
|
||||
gotten[pkgName] = struct{}{}
|
||||
if err := c.PackageResolver(pkgName); err != nil {
|
||||
utils.Logger.Error("Unable to resolve package", "package", pkgName, "error", err)
|
||||
return nil, newCompileError(paths, []byte(err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// Success getting the import, attempt to build again.
|
||||
}
|
||||
|
||||
// TODO remove this unreachable code and document it
|
||||
utils.Logger.Fatal("Not reachable")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Try to define a version string for the compiled app
|
||||
// The following is tried (first match returns):
|
||||
// - Read a version explicitly specified in the APP_VERSION environment
|
||||
// variable
|
||||
// - Read the output of "git describe" if the source is in a git repository
|
||||
// If no version can be determined, an empty string is returned.
|
||||
func getAppVersion(paths *model.RevelContainer) string {
|
||||
if version := os.Getenv("APP_VERSION"); version != "" {
|
||||
return version
|
||||
}
|
||||
|
||||
// Check for the git binary
|
||||
if gitPath, err := exec.LookPath("git"); err == nil {
|
||||
// Check for the .git directory
|
||||
gitDir := filepath.Join(paths.BasePath, ".git")
|
||||
info, err := os.Stat(gitDir)
|
||||
if (err != nil && os.IsNotExist(err)) || !info.IsDir() {
|
||||
return ""
|
||||
}
|
||||
gitCmd := exec.Command(gitPath, "--git-dir=" + gitDir, "--work-tree=" + paths.BasePath, "describe", "--always", "--dirty")
|
||||
utils.Logger.Info("Exec:", "args", gitCmd.Args)
|
||||
output, err := gitCmd.Output()
|
||||
|
||||
if err != nil {
|
||||
utils.Logger.Error("Cannot determine git repository version:", "error", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return "git-" + strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func cleanSource(paths *model.RevelContainer, dirs ...string) {
|
||||
for _, dir := range dirs {
|
||||
cleanDir(paths, dir)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanDir(paths *model.RevelContainer, dir string) {
|
||||
utils.Logger.Info("Cleaning dir ", "dir", dir)
|
||||
tmpPath := filepath.Join(paths.AppPath, dir)
|
||||
f, err := os.Open(tmpPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
utils.Logger.Error("Failed to clean dir:", "error", err)
|
||||
}
|
||||
} else {
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
infos, err := f.Readdir(0)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
utils.Logger.Fatal("Failed to clean dir:", "error", err)
|
||||
}
|
||||
} else {
|
||||
for _, info := range infos {
|
||||
pathName := filepath.Join(tmpPath, info.Name())
|
||||
if info.IsDir() {
|
||||
err := os.RemoveAll(pathName)
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Failed to remove dir:", "error", err)
|
||||
}
|
||||
} else {
|
||||
err := os.Remove(pathName)
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Failed to remove file:", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// genSource renders the given template to produce source code, which it writes
|
||||
// to the given directory and file.
|
||||
func genSource(paths *model.RevelContainer, dir, filename, templateSource string, args map[string]interface{}) error {
|
||||
|
||||
return utils.GenerateTemplate(filepath.Join(paths.AppPath, dir, filename), templateSource, args)
|
||||
}
|
||||
|
||||
// Looks through all the method args and returns a set of unique import paths
|
||||
// that cover all the method arg types.
|
||||
// Additionally, assign package aliases when necessary to resolve ambiguity.
|
||||
func calcImportAliases(src *model.SourceInfo) map[string]string {
|
||||
aliases := make(map[string]string)
|
||||
typeArrays := [][]*model.TypeInfo{src.ControllerSpecs(), src.TestSuites()}
|
||||
for _, specs := range typeArrays {
|
||||
for _, spec := range specs {
|
||||
addAlias(aliases, spec.ImportPath, spec.PackageName)
|
||||
|
||||
for _, methSpec := range spec.MethodSpecs {
|
||||
for _, methArg := range methSpec.Args {
|
||||
if methArg.ImportPath == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
addAlias(aliases, methArg.ImportPath, methArg.TypeExpr.PkgName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the "InitImportPaths", with alias "_"
|
||||
for _, importPath := range src.InitImportPaths {
|
||||
if _, ok := aliases[importPath]; !ok {
|
||||
aliases[importPath] = "_"
|
||||
}
|
||||
}
|
||||
|
||||
return aliases
|
||||
}
|
||||
|
||||
// Adds an alias to the map of alias names
|
||||
func addAlias(aliases map[string]string, importPath, pkgName string) {
|
||||
alias, ok := aliases[importPath]
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
alias = makePackageAlias(aliases, pkgName)
|
||||
aliases[importPath] = alias
|
||||
}
|
||||
|
||||
// Generates a package alias
|
||||
func makePackageAlias(aliases map[string]string, pkgName string) string {
|
||||
i := 0
|
||||
alias := pkgName
|
||||
for containsValue(aliases, alias) || alias == "revel" {
|
||||
alias = fmt.Sprintf("%s%d", pkgName, i)
|
||||
i++
|
||||
}
|
||||
return alias
|
||||
}
|
||||
|
||||
// Returns true if this value is in the map
|
||||
func containsValue(m map[string]string, val string) bool {
|
||||
for _, v := range m {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse the output of the "go build" command.
|
||||
// Return a detailed Error.
|
||||
func newCompileError(paths *model.RevelContainer, output []byte) *utils.SourceError {
|
||||
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
|
||||
FindSubmatch(output)
|
||||
if errorMatch == nil {
|
||||
errorMatch = regexp.MustCompile(`(?m)^(.*?):(\d+):\s(.*?)$`).FindSubmatch(output)
|
||||
|
||||
if errorMatch == nil {
|
||||
utils.Logger.Error("Failed to parse build errors", "error", string(output))
|
||||
return &utils.SourceError{
|
||||
SourceType: "Go code",
|
||||
Title: "Go Compilation Error",
|
||||
Description: "See console for build error.",
|
||||
}
|
||||
}
|
||||
|
||||
errorMatch = append(errorMatch, errorMatch[3])
|
||||
|
||||
utils.Logger.Error("Build errors", "errors", string(output))
|
||||
}
|
||||
|
||||
findInPaths := func(relFilename string) string {
|
||||
// Extract the paths from the gopaths, and search for file there first
|
||||
gopaths := filepath.SplitList(build.Default.GOPATH)
|
||||
for _, gp := range gopaths {
|
||||
newPath := filepath.Join(gp, "src", paths.ImportPath, relFilename)
|
||||
println(newPath)
|
||||
if utils.Exists(newPath) {
|
||||
return newPath
|
||||
}
|
||||
}
|
||||
newPath, _ := filepath.Abs(relFilename)
|
||||
utils.Logger.Warn("Could not find in GO path", "file", relFilename)
|
||||
return newPath
|
||||
}
|
||||
|
||||
|
||||
// Read the source for the offending file.
|
||||
var (
|
||||
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
||||
absFilename = findInPaths(relFilename)
|
||||
line, _ = strconv.Atoi(string(errorMatch[2]))
|
||||
description = string(errorMatch[4])
|
||||
compileError = &utils.SourceError{
|
||||
SourceType: "Go code",
|
||||
Title: "Go Compilation Error",
|
||||
Path: relFilename,
|
||||
Description: description,
|
||||
Line: line,
|
||||
}
|
||||
)
|
||||
|
||||
errorLink := paths.Config.StringDefault("error.link", "")
|
||||
|
||||
if errorLink != "" {
|
||||
compileError.SetLink(errorLink)
|
||||
}
|
||||
|
||||
fileStr, err := utils.ReadLines(absFilename)
|
||||
if err != nil {
|
||||
compileError.MetaError = absFilename + ": " + err.Error()
|
||||
utils.Logger.Info("Unable to readlines " + compileError.MetaError, "error", err)
|
||||
return compileError
|
||||
}
|
||||
|
||||
compileError.SourceLines = fileStr
|
||||
return compileError
|
||||
}
|
||||
|
||||
// RevelMainTemplate template for app/tmp/run/run.go
|
||||
const RevelRunTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||
// This file is the run file for Revel.
|
||||
// It registers all the controllers and provides details for the Revel server engine to
|
||||
// properly inject parameters directly into the action endpoints.
|
||||
package run
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"github.com/revel/revel"{{range $k, $v := $.ImportPaths}}
|
||||
{{$v}} "{{$k}}"{{end}}
|
||||
"github.com/revel/revel/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
// So compiler won't complain if the generated code doesn't reference reflect package...
|
||||
_ = reflect.Invalid
|
||||
)
|
||||
|
||||
// Register and run the application
|
||||
func Run(port int) {
|
||||
Register()
|
||||
revel.Run(port)
|
||||
}
|
||||
|
||||
// Register all the controllers
|
||||
func Register() {
|
||||
revel.AppLog.Info("Running revel server")
|
||||
{{range $i, $c := .Controllers}}
|
||||
revel.RegisterController((*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),
|
||||
[]*revel.MethodType{
|
||||
{{range .MethodSpecs}}&revel.MethodType{
|
||||
Name: "{{.Name}}",
|
||||
Args: []*revel.MethodArg{ {{range .Args}}
|
||||
&revel.MethodArg{Name: "{{.Name}}", Type: reflect.TypeOf((*{{index $.ImportPaths .ImportPath | .TypeExpr.TypeName}})(nil)) },{{end}}
|
||||
},
|
||||
RenderArgNames: map[int][]string{ {{range .RenderCalls}}
|
||||
{{.Line}}: []string{ {{range .Names}}
|
||||
"{{.}}",{{end}}
|
||||
},{{end}}
|
||||
},
|
||||
},
|
||||
{{end}}
|
||||
})
|
||||
{{end}}
|
||||
revel.DefaultValidationKeys = map[string]map[int]string{ {{range $path, $lines := .ValidationKeys}}
|
||||
"{{$path}}": { {{range $line, $key := $lines}}
|
||||
{{$line}}: "{{$key}}",{{end}}
|
||||
},{{end}}
|
||||
}
|
||||
testing.TestSuites = []interface{}{ {{range .TestSuites}}
|
||||
(*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),{{end}}
|
||||
}
|
||||
}
|
||||
`
|
||||
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||
// This file is the main file for Revel.
|
||||
// It registers all the controllers and provides details for the Revel server engine to
|
||||
// properly inject parameters directly into the action endpoints.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"{{.ImportPath}}/app/tmp/run"
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var (
|
||||
runMode *string = flag.String("runMode", "", "Run mode.")
|
||||
port *int = flag.Int("port", 0, "By default, read from app.conf")
|
||||
importPath *string = flag.String("importPath", "", "Go Import Path for the app.")
|
||||
srcPath *string = flag.String("srcPath", "", "Path to the source root.")
|
||||
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
revel.Init(*runMode, *importPath, *srcPath)
|
||||
run.Run(*port)
|
||||
}
|
||||
`
|
||||
|
||||
// RevelRoutesTemplate template for app/conf/routes
|
||||
const RevelRoutesTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||
// This file provides a way of creating URL's based on all the actions
|
||||
// found in all the controllers.
|
||||
package routes
|
||||
|
||||
import "github.com/revel/revel"
|
||||
|
||||
{{range $i, $c := .Controllers}}
|
||||
type t{{.StructName}} struct {}
|
||||
var {{.StructName}} t{{.StructName}}
|
||||
|
||||
{{range .MethodSpecs}}
|
||||
func (_ t{{$c.StructName}}) {{.Name}}({{range .Args}}
|
||||
{{.Name}} {{if .ImportPath}}interface{}{{else}}{{.TypeExpr.TypeName ""}}{{end}},{{end}}
|
||||
) string {
|
||||
args := make(map[string]string)
|
||||
{{range .Args}}
|
||||
revel.Unbind(args, "{{.Name}}", {{.Name}}){{end}}
|
||||
return revel.MainRouter.Reverse("{{$c.StructName}}.{{.Name}}", args).URL
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`
|
||||
411
app/cmd/harness/harness.go
Normal file
411
app/cmd/harness/harness.go
Normal file
@@ -0,0 +1,411 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package harness for a Revel Framework.
|
||||
//
|
||||
// It has a following responsibilities:
|
||||
// 1. Parse the user program, generating a main.go file that registers
|
||||
// controller classes and starts the user's server.
|
||||
// 2. Build and run the user program. Show compile errors.
|
||||
// 3. Monitor the user source and re-build / restart the program when necessary.
|
||||
//
|
||||
// Source files are generated in the app/tmp directory.
|
||||
package harness
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"time"
|
||||
"go/build"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"github.com/revel/cmd/watcher"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
var (
|
||||
doNotWatch = []string{"tmp", "views", "routes"}
|
||||
|
||||
lastRequestHadError int32
|
||||
)
|
||||
|
||||
// Harness reverse proxies requests to the application server.
|
||||
// It builds / runs / rebuilds / restarts the server when code is changed.
|
||||
type Harness struct {
|
||||
app *App // The application
|
||||
useProxy bool // True if proxy is in use
|
||||
serverHost string // The proxy server host
|
||||
port int // The proxy serber port
|
||||
proxy *httputil.ReverseProxy // The proxy
|
||||
watcher *watcher.Watcher // The file watched
|
||||
mutex *sync.Mutex // A mutex to prevent concurrent updates
|
||||
paths *model.RevelContainer // The Revel container
|
||||
config *model.CommandConfig // The configuration
|
||||
runMode string // The runmode the harness is running in
|
||||
isError bool // True if harness is in error state
|
||||
ranOnce bool // True app compiled once
|
||||
}
|
||||
|
||||
func (h *Harness) renderError(iw http.ResponseWriter, ir *http.Request, err error) {
|
||||
// Render error here
|
||||
// Grab the template from three places
|
||||
// 1) Application/views/errors
|
||||
// 2) revel_home/views/errors
|
||||
// 3) views/errors
|
||||
if err == nil {
|
||||
utils.Logger.Panic("Caller passed in a nil error")
|
||||
}
|
||||
templateSet := template.New("__root__")
|
||||
seekViewOnPath := func(view string) (path string) {
|
||||
path = filepath.Join(h.paths.ViewsPath, "errors", view)
|
||||
if !utils.Exists(path) {
|
||||
path = filepath.Join(h.paths.RevelPath, "templates", "errors", view)
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Unable to read template file", path)
|
||||
}
|
||||
_, err = templateSet.New("errors/" + view).Parse(string(data))
|
||||
if err != nil {
|
||||
utils.Logger.Error("Unable to parse template file", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
target := []string{seekViewOnPath("500.html"), seekViewOnPath("500-dev.html")}
|
||||
if !utils.Exists(target[0]) {
|
||||
fmt.Fprintf(iw, "Target template not found not found %s<br />\n", target[0])
|
||||
fmt.Fprintf(iw, "An error ocurred %s", err.Error())
|
||||
return
|
||||
}
|
||||
var revelError *utils.SourceError
|
||||
switch e := err.(type) {
|
||||
case *utils.SourceError:
|
||||
revelError = e
|
||||
case error:
|
||||
revelError = &utils.SourceError{
|
||||
Title: "Server Error",
|
||||
Description: e.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
if revelError == nil {
|
||||
panic("no error provided")
|
||||
}
|
||||
viewArgs := map[string]interface{}{}
|
||||
viewArgs["RunMode"] = h.paths.RunMode
|
||||
viewArgs["DevMode"] = h.paths.DevMode
|
||||
viewArgs["Error"] = revelError
|
||||
|
||||
// Render the template from the file
|
||||
err = templateSet.ExecuteTemplate(iw, "errors/500.html", viewArgs)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Failed to execute", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP handles all requests.
|
||||
// It checks for changes to app, rebuilds if necessary, and forwards the request.
|
||||
func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Don't rebuild the app for favicon requests.
|
||||
if lastRequestHadError > 0 && r.URL.Path == "/favicon.ico" {
|
||||
return
|
||||
}
|
||||
|
||||
// Flush any change events and rebuild app if necessary.
|
||||
// Render an error page if the rebuild / restart failed.
|
||||
err := h.watcher.Notify()
|
||||
if err != nil {
|
||||
// In a thread safe manner update the flag so that a request for
|
||||
// /favicon.ico does not trigger a rebuild
|
||||
atomic.CompareAndSwapInt32(&lastRequestHadError, 0, 1)
|
||||
h.renderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// In a thread safe manner update the flag so that a request for
|
||||
// /favicon.ico is allowed
|
||||
atomic.CompareAndSwapInt32(&lastRequestHadError, 1, 0)
|
||||
|
||||
// Reverse proxy the request.
|
||||
// (Need special code for websockets, courtesy of bradfitz)
|
||||
if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
||||
h.proxyWebsocket(w, r, h.serverHost)
|
||||
} else {
|
||||
h.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// NewHarness method returns a reverse proxy that forwards requests
|
||||
// to the given port.
|
||||
func NewHarness(c *model.CommandConfig, paths *model.RevelContainer, runMode string, noProxy bool) *Harness {
|
||||
// Get a template loader to render errors.
|
||||
// Prefer the app's views/errors directory, and fall back to the stock error pages.
|
||||
//revel.MainTemplateLoader = revel.NewTemplateLoader(
|
||||
// []string{filepath.Join(revel.RevelPath, "templates")})
|
||||
//if err := revel.MainTemplateLoader.Refresh(); err != nil {
|
||||
// revel.RevelLog.Error("Template loader error", "error", err)
|
||||
//}
|
||||
|
||||
addr := paths.HTTPAddr
|
||||
port := paths.Config.IntDefault("harness.port", 0)
|
||||
scheme := "http"
|
||||
|
||||
if paths.HTTPSsl {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
// If the server is running on the wildcard address, use "localhost"
|
||||
if addr == "" {
|
||||
utils.Logger.Warn("No http.addr specified in the app.conf listening on localhost interface only. " +
|
||||
"This will not allow external access to your application")
|
||||
addr = "localhost"
|
||||
}
|
||||
|
||||
if port == 0 {
|
||||
port = getFreePort()
|
||||
}
|
||||
|
||||
serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
|
||||
|
||||
serverHarness := &Harness{
|
||||
port: port,
|
||||
serverHost: serverURL.String()[len(scheme+"://"):],
|
||||
proxy: httputil.NewSingleHostReverseProxy(serverURL),
|
||||
mutex: &sync.Mutex{},
|
||||
paths: paths,
|
||||
useProxy: !noProxy,
|
||||
config: c,
|
||||
runMode: runMode,
|
||||
}
|
||||
|
||||
if paths.HTTPSsl {
|
||||
serverHarness.proxy.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
return serverHarness
|
||||
}
|
||||
|
||||
// Refresh method rebuilds the Revel application and run it on the given port.
|
||||
// called by the watcher
|
||||
func (h *Harness) Refresh() (err *utils.SourceError) {
|
||||
t := time.Now();
|
||||
fmt.Println("Changed detected, recompiling")
|
||||
err = h.refresh()
|
||||
if err!=nil && !h.ranOnce && h.useProxy {
|
||||
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
|
||||
|
||||
fmt.Printf("\nError compiling code, to view error details see proxy running on http://%s\n\n",addr)
|
||||
}
|
||||
|
||||
h.ranOnce = true
|
||||
fmt.Printf("\nTime to recompile %s\n",time.Now().Sub(t).String())
|
||||
return
|
||||
}
|
||||
|
||||
func (h *Harness) refresh() (err *utils.SourceError) {
|
||||
// Allow only one thread to rebuild the process
|
||||
// If multiple requests to rebuild are queued only the last one is executed on
|
||||
// So before a build is started we wait for a second to determine if
|
||||
// more requests for a build are triggered.
|
||||
// Once no more requests are triggered the build will be processed
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.app != nil {
|
||||
h.app.Kill()
|
||||
}
|
||||
|
||||
utils.Logger.Info("Rebuild Called")
|
||||
var newErr error
|
||||
h.app, newErr = Build(h.config, h.paths)
|
||||
if newErr != nil {
|
||||
utils.Logger.Error("Build detected an error", "error", newErr)
|
||||
if castErr, ok := newErr.(*utils.SourceError); ok {
|
||||
return castErr
|
||||
}
|
||||
err = &utils.SourceError{
|
||||
Title: "App failed to start up",
|
||||
Description: err.Error(),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if h.useProxy {
|
||||
h.app.Port = h.port
|
||||
runMode := h.runMode
|
||||
if !h.config.HistoricMode {
|
||||
// Recalulate run mode based on the config
|
||||
var paths []byte
|
||||
if len(h.app.PackagePathMap)>0 {
|
||||
paths, _ = json.Marshal(h.app.PackagePathMap)
|
||||
}
|
||||
runMode = fmt.Sprintf(`{"mode":"%s", "specialUseFlag":%v,"packagePathMap":%s}`, h.app.Paths.RunMode, h.config.Verbose, string(paths))
|
||||
|
||||
}
|
||||
if err2 := h.app.Cmd(runMode).Start(h.config); err2 != nil {
|
||||
utils.Logger.Error("Could not start application", "error", err2)
|
||||
if err,k :=err2.(*utils.SourceError);k {
|
||||
return err
|
||||
}
|
||||
return &utils.SourceError{
|
||||
Title: "App failed to start up",
|
||||
Description: err2.Error(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
h.app = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WatchDir method returns false to file matches with doNotWatch
|
||||
// otheriwse true
|
||||
func (h *Harness) WatchDir(info os.FileInfo) bool {
|
||||
return !utils.ContainsString(doNotWatch, info.Name())
|
||||
}
|
||||
|
||||
// WatchFile method returns true given filename HasSuffix of ".go"
|
||||
// otheriwse false - implements revel.DiscerningListener
|
||||
func (h *Harness) WatchFile(filename string) bool {
|
||||
return strings.HasSuffix(filename, ".go")
|
||||
}
|
||||
|
||||
// Run the harness, which listens for requests and proxies them to the app
|
||||
// server, which it runs and rebuilds as necessary.
|
||||
func (h *Harness) Run() {
|
||||
var paths []string
|
||||
if h.paths.Config.BoolDefault("watch.gopath", false) {
|
||||
gopaths := filepath.SplitList(build.Default.GOPATH)
|
||||
paths = append(paths, gopaths...)
|
||||
}
|
||||
paths = append(paths, h.paths.CodePaths...)
|
||||
h.watcher = watcher.NewWatcher(h.paths, false)
|
||||
h.watcher.Listen(h, paths...)
|
||||
go h.Refresh()
|
||||
// h.watcher.Notify()
|
||||
|
||||
if h.useProxy {
|
||||
go func() {
|
||||
// Check the port to start on a random port
|
||||
if h.paths.HTTPPort == 0 {
|
||||
h.paths.HTTPPort = getFreePort()
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", h.paths.HTTPAddr, h.paths.HTTPPort)
|
||||
utils.Logger.Infof("Proxy server is listening on %s", addr)
|
||||
|
||||
|
||||
var err error
|
||||
if h.paths.HTTPSsl {
|
||||
err = http.ListenAndServeTLS(
|
||||
addr,
|
||||
h.paths.HTTPSslCert,
|
||||
h.paths.HTTPSslKey,
|
||||
h)
|
||||
} else {
|
||||
err = http.ListenAndServe(addr, h)
|
||||
}
|
||||
if err != nil {
|
||||
utils.Logger.Error("Failed to start reverse proxy:", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// Make a new channel to listen for the interrupt event
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, os.Interrupt, os.Kill)
|
||||
<-ch
|
||||
// Kill the app and exit
|
||||
if h.app != nil {
|
||||
h.app.Kill()
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Find an unused port
|
||||
func getFreePort() (port int) {
|
||||
conn, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Unable to fetch a freee port address", "error", err)
|
||||
}
|
||||
|
||||
port = conn.Addr().(*net.TCPAddr).Port
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
utils.Logger.Fatal("Unable to close port", "error", err)
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
// proxyWebsocket copies data between websocket client and server until one side
|
||||
// closes the connection. (ReverseProxy doesn't work with websocket requests.)
|
||||
func (h *Harness) proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
||||
var (
|
||||
d net.Conn
|
||||
err error
|
||||
)
|
||||
if h.paths.HTTPSsl {
|
||||
// since this proxy isn't used in production,
|
||||
// it's OK to set InsecureSkipVerify to true
|
||||
// no need to add another configuration option.
|
||||
d, err = tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true})
|
||||
} else {
|
||||
d, err = net.Dial("tcp", host)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, "Error contacting backend server.", 500)
|
||||
utils.Logger.Error("Error dialing websocket backend ", "host", host, "error", err)
|
||||
return
|
||||
}
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
http.Error(w, "Not a hijacker?", 500)
|
||||
return
|
||||
}
|
||||
nc, _, err := hj.Hijack()
|
||||
if err != nil {
|
||||
utils.Logger.Error("Hijack error", "error", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err = nc.Close(); err != nil {
|
||||
utils.Logger.Error("Connection close error", "error", err)
|
||||
}
|
||||
if err = d.Close(); err != nil {
|
||||
utils.Logger.Error("Dial close error", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = r.Write(d)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Error copying request to target", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
errc := make(chan error, 2)
|
||||
cp := func(dst io.Writer, src io.Reader) {
|
||||
_, err := io.Copy(dst, src)
|
||||
errc <- err
|
||||
}
|
||||
go cp(d, nc)
|
||||
go cp(nc, d)
|
||||
<-errc
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/revel/cmd/harness"
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// go run main.go
|
||||
// 生成routes.go, main.go
|
||||
revel.Init("", "github.com/leanote/leanote", "")
|
||||
_, err := harness.Build() // ok, err
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Ok")
|
||||
// panicOnError(reverr, "Failed to build")
|
||||
}
|
||||
427
app/cmd/parser2/source_info_processor.go
Normal file
427
app/cmd/parser2/source_info_processor.go
Normal file
@@ -0,0 +1,427 @@
|
||||
package parser2
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/utils"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"github.com/revel/cmd/model"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
"path/filepath"
|
||||
"github.com/revel/cmd/logger"
|
||||
)
|
||||
|
||||
type (
|
||||
SourceInfoProcessor struct {
|
||||
sourceProcessor *SourceProcessor
|
||||
}
|
||||
)
|
||||
|
||||
func NewSourceInfoProcessor(sourceProcessor *SourceProcessor) *SourceInfoProcessor {
|
||||
return &SourceInfoProcessor{sourceProcessor:sourceProcessor}
|
||||
}
|
||||
|
||||
func (s *SourceInfoProcessor) processPackage(p *packages.Package) (sourceInfo *model.SourceInfo) {
|
||||
sourceInfo = &model.SourceInfo{
|
||||
ValidationKeys: map[string]map[int]string{},
|
||||
}
|
||||
var (
|
||||
isController = strings.HasSuffix(p.PkgPath, "/controllers") ||
|
||||
strings.Contains(p.PkgPath, "/controllers/")
|
||||
isTest = strings.HasSuffix(p.PkgPath, "/tests") ||
|
||||
strings.Contains(p.PkgPath, "/tests/")
|
||||
methodMap = map[string][]*model.MethodSpec{}
|
||||
)
|
||||
localImportMap := map[string]string{}
|
||||
log := s.sourceProcessor.log.New("package", p.PkgPath)
|
||||
log.Info("Processing package")
|
||||
for _, tree := range p.Syntax {
|
||||
for _, decl := range tree.Decls {
|
||||
|
||||
s.sourceProcessor.packageMap[p.PkgPath] = filepath.Dir(p.Fset.Position(decl.Pos()).Filename)
|
||||
if !s.addImport(decl, p, localImportMap, log) {
|
||||
continue
|
||||
}
|
||||
spec, found := s.getStructTypeDecl(decl, p.Fset)
|
||||
//log.Info("Checking file","filename", p.Fset.Position(decl.Pos()).Filename,"found",found)
|
||||
if found {
|
||||
if isController || isTest {
|
||||
controllerSpec := s.getControllerSpec(spec, p, localImportMap)
|
||||
sourceInfo.StructSpecs = append(sourceInfo.StructSpecs, controllerSpec)
|
||||
}
|
||||
} else {
|
||||
// Not a type definition, this could be a method for a controller try to extract that
|
||||
// Func declaration?
|
||||
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// This could be a controller action endpoint, check and add if needed
|
||||
if isController &&
|
||||
funcDecl.Recv != nil && // Must have a receiver
|
||||
funcDecl.Name.IsExported() && // be public
|
||||
funcDecl.Type.Results != nil && len(funcDecl.Type.Results.List) == 1 {
|
||||
// return one result
|
||||
if m, receiver := s.getControllerFunc(funcDecl, p, localImportMap); m != nil {
|
||||
methodMap[receiver] = append(methodMap[receiver], m)
|
||||
log.Info("Added method map to ", "receiver", receiver, "method", m.Name)
|
||||
}
|
||||
}
|
||||
// Check for validation
|
||||
if lineKeyMap := s.getValidation(funcDecl, p); len(lineKeyMap) > 1 {
|
||||
sourceInfo.ValidationKeys[p.PkgPath + "." + s.getFuncName(funcDecl)] = lineKeyMap
|
||||
}
|
||||
if funcDecl.Name.Name == "init" {
|
||||
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, p.PkgPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the method specs to the struct specs.
|
||||
for _, spec := range sourceInfo.StructSpecs {
|
||||
spec.MethodSpecs = methodMap[spec.StructName]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
// Scan app source code for calls to X.Y(), where X is of type *Validation.
|
||||
//
|
||||
// Recognize these scenarios:
|
||||
// - "Y" = "Validation" and is a member of the receiver.
|
||||
// (The common case for inline validation)
|
||||
// - "X" is passed in to the func as a parameter.
|
||||
// (For structs implementing Validated)
|
||||
//
|
||||
// The line number to which a validation call is attributed is that of the
|
||||
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
|
||||
// reports.
|
||||
//
|
||||
// The end result is that we can set the default validation key for each call to
|
||||
// be the same as the local variable.
|
||||
func (s *SourceInfoProcessor) getValidation(funcDecl *ast.FuncDecl, p *packages.Package) (map[int]string) {
|
||||
var (
|
||||
lineKeys = make(map[int]string)
|
||||
|
||||
// Check the func parameters and the receiver's members for the *revel.Validation type.
|
||||
validationParam = s.getValidationParameter(funcDecl)
|
||||
)
|
||||
|
||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||
// e.g. c.Validation.Required(arg) or v.Required(arg)
|
||||
callExpr, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// e.g. c.Validation.Required or v.Required
|
||||
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
switch x := funcSelector.X.(type) {
|
||||
case *ast.SelectorExpr: // e.g. c.Validation
|
||||
if x.Sel.Name != "Validation" {
|
||||
return true
|
||||
}
|
||||
|
||||
case *ast.Ident: // e.g. v
|
||||
if validationParam == nil || x.Obj != validationParam {
|
||||
return true
|
||||
}
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
||||
if len(callExpr.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Given the validation expression, extract the key.
|
||||
key := callExpr.Args[0]
|
||||
switch expr := key.(type) {
|
||||
case *ast.BinaryExpr:
|
||||
// If the argument is a binary expression, take the first expression.
|
||||
// (e.g. c.Validation.Required(myName != ""))
|
||||
key = expr.X
|
||||
case *ast.UnaryExpr:
|
||||
// If the argument is a unary expression, drill in.
|
||||
// (e.g. c.Validation.Required(!myBool)
|
||||
key = expr.X
|
||||
case *ast.BasicLit:
|
||||
// If it's a literal, skip it.
|
||||
return true
|
||||
}
|
||||
|
||||
if typeExpr := model.NewTypeExprFromAst("", key); typeExpr.Valid {
|
||||
lineKeys[p.Fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
|
||||
} else {
|
||||
s.sourceProcessor.log.Error("Error: Failed to generate key for field validation. Make sure the field name is valid.", "file", p.PkgPath,
|
||||
"line", p.Fset.Position(callExpr.End()).Line, "function", funcDecl.Name.String())
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return lineKeys
|
||||
|
||||
}
|
||||
// Check to see if there is a *revel.Validation as an argument.
|
||||
func (s *SourceInfoProcessor) getValidationParameter(funcDecl *ast.FuncDecl) *ast.Object {
|
||||
for _, field := range funcDecl.Type.Params.List {
|
||||
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if selExpr.Sel.Name == "Validation" && s.sourceProcessor.importMap[xIdent.Name] == model.RevelImportPath {
|
||||
return field.Names[0].Obj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (s *SourceInfoProcessor) getControllerFunc(funcDecl *ast.FuncDecl, p *packages.Package, localImportMap map[string]string) (method *model.MethodSpec, recvTypeName string) {
|
||||
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if selExpr.Sel.Name != "Result" {
|
||||
return
|
||||
}
|
||||
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || s.sourceProcessor.importMap[pkgIdent.Name] != model.RevelImportPath {
|
||||
return
|
||||
}
|
||||
method = &model.MethodSpec{
|
||||
Name: funcDecl.Name.Name,
|
||||
}
|
||||
|
||||
// Add a description of the arguments to the method.
|
||||
for _, field := range funcDecl.Type.Params.List {
|
||||
for _, name := range field.Names {
|
||||
var importPath string
|
||||
typeExpr := model.NewTypeExprFromAst(p.Name, field.Type)
|
||||
if !typeExpr.Valid {
|
||||
utils.Logger.Warn("Warn: Didn't understand argument '%s' of action %s. Ignoring.", name, s.getFuncName(funcDecl))
|
||||
return // We didn't understand one of the args. Ignore this action.
|
||||
}
|
||||
// Local object
|
||||
if typeExpr.PkgName == p.Name {
|
||||
importPath = p.PkgPath
|
||||
} else if typeExpr.PkgName != "" {
|
||||
var ok bool
|
||||
if importPath, ok = localImportMap[typeExpr.PkgName]; !ok {
|
||||
if importPath, ok = s.sourceProcessor.importMap[typeExpr.PkgName]; !ok {
|
||||
utils.Logger.Error("Unable to find import", "importMap", s.sourceProcessor.importMap, "localimport", localImportMap)
|
||||
utils.Logger.Fatalf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||
}
|
||||
}
|
||||
}
|
||||
method.Args = append(method.Args, &model.MethodArg{
|
||||
Name: name.Name,
|
||||
TypeExpr: typeExpr,
|
||||
ImportPath: importPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add a description of the calls to Render from the method.
|
||||
// Inspect every node (e.g. always return true).
|
||||
method.RenderCalls = []*model.MethodCall{}
|
||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||
// Is it a function call?
|
||||
callExpr, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Is it calling (*Controller).Render?
|
||||
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// The type of the receiver is not easily available, so just store every
|
||||
// call to any method called Render.
|
||||
if selExpr.Sel.Name != "Render" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Add this call's args to the renderArgs.
|
||||
pos := p.Fset.Position(callExpr.Lparen)
|
||||
methodCall := &model.MethodCall{
|
||||
Line: pos.Line,
|
||||
Names: []string{},
|
||||
}
|
||||
for _, arg := range callExpr.Args {
|
||||
argIdent, ok := arg.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
methodCall.Names = append(methodCall.Names, argIdent.Name)
|
||||
}
|
||||
method.RenderCalls = append(method.RenderCalls, methodCall)
|
||||
return true
|
||||
})
|
||||
|
||||
var recvType = funcDecl.Recv.List[0].Type
|
||||
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
||||
} else {
|
||||
recvTypeName = recvType.(*ast.Ident).Name
|
||||
}
|
||||
return
|
||||
}
|
||||
func (s *SourceInfoProcessor) getControllerSpec(spec *ast.TypeSpec, p *packages.Package, localImportMap map[string]string) (controllerSpec *model.TypeInfo) {
|
||||
structType := spec.Type.(*ast.StructType)
|
||||
|
||||
// At this point we know it's a type declaration for a struct.
|
||||
// Fill in the rest of the info by diving into the fields.
|
||||
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
||||
controllerSpec = &model.TypeInfo{
|
||||
StructName: spec.Name.Name,
|
||||
ImportPath: p.PkgPath,
|
||||
PackageName: p.Name,
|
||||
}
|
||||
log := s.sourceProcessor.log.New("file", p.Fset.Position(spec.Pos()).Filename, "position", p.Fset.Position(spec.Pos()).Line)
|
||||
for _, field := range structType.Fields.List {
|
||||
// If field.Names is set, it's not an embedded type.
|
||||
if field.Names != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// A direct "sub-type" has an ast.Field as either:
|
||||
// Ident { "AppController" }
|
||||
// SelectorExpr { "rev", "Controller" }
|
||||
// Additionally, that can be wrapped by StarExprs.
|
||||
fieldType := field.Type
|
||||
pkgName, typeName := func() (string, string) {
|
||||
// Drill through any StarExprs.
|
||||
for {
|
||||
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
|
||||
fieldType = starExpr.X
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// If the embedded type is in the same package, it's an Ident.
|
||||
if ident, ok := fieldType.(*ast.Ident); ok {
|
||||
return "", ident.Name
|
||||
}
|
||||
|
||||
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
|
||||
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
|
||||
return pkgIdent.Name, selectorExpr.Sel.Name
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}()
|
||||
|
||||
// If a typename wasn't found, skip it.
|
||||
if typeName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the import path for this type.
|
||||
// If it was referenced without a package name, use the current package import path.
|
||||
// Else, look up the package's import path by name.
|
||||
var importPath string
|
||||
if pkgName == "" {
|
||||
importPath = p.PkgPath
|
||||
} else {
|
||||
var ok bool
|
||||
if importPath, ok = localImportMap[pkgName]; !ok {
|
||||
log.Debug("Debug: Unusual, failed to find package locally ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin", )
|
||||
if importPath, ok = s.sourceProcessor.importMap[pkgName]; !ok {
|
||||
log.Error("Error: Failed to find import path for ", "package", pkgName, "type", typeName, "map", s.sourceProcessor.importMap, "usedin", )
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
controllerSpec.EmbeddedTypes = append(controllerSpec.EmbeddedTypes, &model.EmbeddedTypeName{
|
||||
ImportPath: importPath,
|
||||
StructName: typeName,
|
||||
})
|
||||
}
|
||||
s.sourceProcessor.log.Info("Added controller spec", "name", controllerSpec.StructName, "package", controllerSpec.ImportPath)
|
||||
return
|
||||
}
|
||||
func (s *SourceInfoProcessor) getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if genDecl.Tok != token.TYPE {
|
||||
return
|
||||
}
|
||||
|
||||
if len(genDecl.Specs) == 0 {
|
||||
utils.Logger.Warn("Warn: Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
||||
return
|
||||
}
|
||||
|
||||
spec = genDecl.Specs[0].(*ast.TypeSpec)
|
||||
_, found = spec.Type.(*ast.StructType)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
func (s *SourceInfoProcessor) getFuncName(funcDecl *ast.FuncDecl) string {
|
||||
prefix := ""
|
||||
if funcDecl.Recv != nil {
|
||||
recvType := funcDecl.Recv.List[0].Type
|
||||
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||
prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")"
|
||||
} else {
|
||||
prefix = recvType.(*ast.Ident).Name
|
||||
}
|
||||
prefix += "."
|
||||
}
|
||||
return prefix + funcDecl.Name.Name
|
||||
}
|
||||
func (s *SourceInfoProcessor) addImport(decl ast.Decl, p *packages.Package, localImportMap map[string]string, log logger.MultiLogger) (shouldContinue bool) {
|
||||
shouldContinue = true
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if genDecl.Tok == token.IMPORT {
|
||||
shouldContinue = false
|
||||
for _, spec := range genDecl.Specs {
|
||||
importSpec := spec.(*ast.ImportSpec)
|
||||
//fmt.Printf("*** import specification %#v\n", importSpec)
|
||||
var pkgAlias string
|
||||
if importSpec.Name != nil {
|
||||
pkgAlias = importSpec.Name.Name
|
||||
if pkgAlias == "_" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
|
||||
fullPath := quotedPath[1 : len(quotedPath) - 1] // Remove the quotes
|
||||
if pkgAlias == "" {
|
||||
pkgAlias = fullPath
|
||||
if index := strings.LastIndex(pkgAlias, "/"); index > 0 {
|
||||
pkgAlias = pkgAlias[index + 1:]
|
||||
}
|
||||
}
|
||||
localImportMap[pkgAlias] = fullPath
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
310
app/cmd/parser2/source_processor.go
Normal file
310
app/cmd/parser2/source_processor.go
Normal file
@@ -0,0 +1,310 @@
|
||||
package parser2
|
||||
|
||||
import (
|
||||
"github.com/revel/cmd/logger"
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
SourceProcessor struct {
|
||||
revelContainer *model.RevelContainer
|
||||
log logger.MultiLogger
|
||||
packageList []*packages.Package
|
||||
importMap map[string]string
|
||||
packageMap map[string]string
|
||||
sourceInfoProcessor *SourceInfoProcessor
|
||||
sourceInfo *model.SourceInfo
|
||||
}
|
||||
)
|
||||
|
||||
func ProcessSource(revelContainer *model.RevelContainer) (sourceInfo *model.SourceInfo, compileError error) {
|
||||
utils.Logger.Info("ProcessSource")
|
||||
processor := NewSourceProcessor(revelContainer)
|
||||
compileError = processor.parse()
|
||||
sourceInfo = processor.sourceInfo
|
||||
if compileError == nil {
|
||||
processor.log.Infof("From parsers : Structures:%d InitImports:%d ValidationKeys:%d %v", len(sourceInfo.StructSpecs), len(sourceInfo.InitImportPaths), len(sourceInfo.ValidationKeys), sourceInfo.PackageMap)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func NewSourceProcessor(revelContainer *model.RevelContainer) *SourceProcessor {
|
||||
s := &SourceProcessor{revelContainer:revelContainer, log:utils.Logger.New("parser", "SourceProcessor")}
|
||||
s.sourceInfoProcessor = NewSourceInfoProcessor(s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *SourceProcessor) parse() (compileError error) {
|
||||
print("Parsing packages, (may require download if not cached)...")
|
||||
if compileError = s.addPackages(); compileError != nil {
|
||||
return
|
||||
}
|
||||
println(" Completed")
|
||||
if compileError = s.addImportMap(); compileError != nil {
|
||||
return
|
||||
}
|
||||
if compileError = s.addSourceInfo(); compileError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.sourceInfo.PackageMap = map[string]string{}
|
||||
getImportFromMap := func(packagePath string) string {
|
||||
for path := range s.packageMap {
|
||||
if strings.Index(path, packagePath) == 0 {
|
||||
fullPath := s.packageMap[path]
|
||||
return fullPath[:(len(fullPath) - len(path) + len(packagePath))]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
s.sourceInfo.PackageMap[model.RevelImportPath] = getImportFromMap(model.RevelImportPath)
|
||||
s.sourceInfo.PackageMap[s.revelContainer.ImportPath] = getImportFromMap(s.revelContainer.ImportPath)
|
||||
for _, module := range s.revelContainer.ModulePathMap {
|
||||
s.sourceInfo.PackageMap[module.ImportPath] = getImportFromMap(module.ImportPath)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 这两个方法来自util
|
||||
|
||||
// Shortcut to fsWalk
|
||||
func (s *SourceProcessor) Walk(root string, walkFn filepath.WalkFunc) error {
|
||||
return s.fsWalk(root, root, walkFn)
|
||||
}
|
||||
|
||||
// Walk the path tree using the function
|
||||
// Every file found will call the function
|
||||
func (s *SourceProcessor) fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
|
||||
fsWalkFunc := func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var name string
|
||||
name, err = filepath.Rel(fname, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
path = filepath.Join(linkName, name)
|
||||
|
||||
// 改了这里
|
||||
if strings.Contains(path, "/leanote/public") ||
|
||||
strings.Contains(path, "/leanote/files") ||
|
||||
strings.Contains(path, "/leanote/doc") ||
|
||||
strings.Contains(path, "/leanote/logs") ||
|
||||
strings.Contains(path, "/leanote/build") ||
|
||||
strings.Contains(path, "/leanote/target") {
|
||||
s.log.Warn("public 或 files 不要处理", "path", path)
|
||||
return filepath.SkipDir
|
||||
}
|
||||
|
||||
if err == nil && info.Mode() & os.ModeSymlink == os.ModeSymlink {
|
||||
var symlinkPath string
|
||||
symlinkPath, err = filepath.EvalSymlinks(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// https://github.com/golang/go/blob/master/src/path/filepath/path.go#L392
|
||||
info, err = os.Lstat(symlinkPath)
|
||||
|
||||
if err != nil {
|
||||
return walkFn(path, info, err)
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return s.fsWalk(symlinkPath, path, walkFn)
|
||||
}
|
||||
}
|
||||
|
||||
return walkFn(path, info, err)
|
||||
}
|
||||
err := filepath.Walk(fname, fsWalkFunc)
|
||||
return err
|
||||
}
|
||||
|
||||
// Using the packages.Load function load all the packages and type specifications (forces compile).
|
||||
// this sets the SourceProcessor.packageList []*packages.Package
|
||||
func (s *SourceProcessor) addPackages() (err error) {
|
||||
allPackages := []string{model.RevelImportPath + "/..."}
|
||||
for _, module := range s.revelContainer.ModulePathMap {
|
||||
allPackages = append(allPackages, module.ImportPath + "/...") // +"/app/controllers/...")
|
||||
}
|
||||
s.log.Info("Reading packages", "packageList", allPackages)
|
||||
//allPackages = []string{s.revelContainer.ImportPath + "/..."} //+"/app/controllers/..."}
|
||||
|
||||
config := &packages.Config{
|
||||
// ode: packages.NeedSyntax | packages.NeedCompiledGoFiles,
|
||||
Mode:
|
||||
packages.NeedTypes | // For compile error
|
||||
packages.NeedDeps | // To load dependent files
|
||||
packages.NeedName | // Loads the full package name
|
||||
packages.NeedSyntax, // To load ast tree (for end points)
|
||||
//Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles |
|
||||
// packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile |
|
||||
// packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo |
|
||||
// packages.NeedTypesSizes,
|
||||
|
||||
//Mode: packages.NeedName | packages.NeedImports | packages.NeedDeps | packages.NeedExportsFile | packages.NeedFiles |
|
||||
// packages.NeedCompiledGoFiles | packages.NeedTypesSizes |
|
||||
// packages.NeedSyntax | packages.NeedCompiledGoFiles ,
|
||||
//Mode: packages.NeedSyntax | packages.NeedCompiledGoFiles | packages.NeedName | packages.NeedFiles |
|
||||
// packages.LoadTypes | packages.NeedTypes | packages.NeedDeps, //, // |
|
||||
// packages.NeedTypes, // packages.LoadTypes | packages.NeedSyntax | packages.NeedTypesInfo,
|
||||
//packages.LoadSyntax | packages.NeedDeps,
|
||||
Dir:s.revelContainer.AppPath,
|
||||
}
|
||||
s.packageList, err = packages.Load(config, allPackages...)
|
||||
s.log.Info("Loaded modules ", "len results", len(s.packageList), "error", err)
|
||||
|
||||
// Now process the files in the aap source folder s.revelContainer.ImportPath + "/...",
|
||||
err = s.Walk(s.revelContainer.BasePath, s.processPath)
|
||||
s.log.Info("Loaded apps and modules ", "len results", len(s.packageList), "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// This callback is used to build the packages for the "app" package. This allows us to
|
||||
// parse the source files without doing a full compile on them
|
||||
// This callback only processes folders, so any files passed to this will return a nil
|
||||
func (s *SourceProcessor) processPath(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
s.log.Error("Error scanning app source:", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore files and folders not marked tmp (since those are generated)
|
||||
if !info.IsDir() || info.Name() == "tmp" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Real work for processing the folder
|
||||
pkgImportPath := s.revelContainer.ImportPath
|
||||
appPath := s.revelContainer.BasePath
|
||||
if appPath != path {
|
||||
pkgImportPath = s.revelContainer.ImportPath + "/" + filepath.ToSlash(path[len(appPath) + 1:])
|
||||
}
|
||||
s.log.Info("Processing source package folder", "package", pkgImportPath, "path", path)
|
||||
|
||||
// Parse files within the path.
|
||||
var pkgMap map[string]*ast.Package
|
||||
fset := token.NewFileSet()
|
||||
pkgMap, err = parser.ParseDir(
|
||||
fset,
|
||||
path,
|
||||
func(f os.FileInfo) bool {
|
||||
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
|
||||
},
|
||||
0)
|
||||
|
||||
if err != nil {
|
||||
if errList, ok := err.(scanner.ErrorList); ok {
|
||||
var pos = errList[0].Pos
|
||||
newError := &utils.SourceError{
|
||||
SourceType: ".go source",
|
||||
Title: "Go Compilation Error",
|
||||
Path: pos.Filename,
|
||||
Description: errList[0].Msg,
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
SourceLines: utils.MustReadLines(pos.Filename),
|
||||
}
|
||||
|
||||
errorLink := s.revelContainer.Config.StringDefault("error.link", "")
|
||||
if errorLink != "" {
|
||||
newError.SetLink(errorLink)
|
||||
}
|
||||
return newError
|
||||
}
|
||||
|
||||
// This is exception, err already checked above. Here just a print
|
||||
ast.Print(nil, err)
|
||||
s.log.Fatal("Failed to parse dir", "error", err)
|
||||
}
|
||||
|
||||
// Skip "main" packages.
|
||||
delete(pkgMap, "main")
|
||||
|
||||
// Ignore packages that end with _test
|
||||
// These cannot be included in source code that is not generated specifically as a test
|
||||
for i := range pkgMap {
|
||||
if len(i) > 6 {
|
||||
if string(i[len(i) - 5:]) == "_test" {
|
||||
delete(pkgMap, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no code in this directory, skip it.
|
||||
if len(pkgMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// There should be only one package in this directory.
|
||||
if len(pkgMap) > 1 {
|
||||
for i := range pkgMap {
|
||||
println("Found duplicate packages in single directory ", i)
|
||||
}
|
||||
utils.Logger.Fatal("Most unexpected! Multiple packages in a single directory:", "packages", pkgMap)
|
||||
}
|
||||
|
||||
// At this point there is only one package in the pkgs map,
|
||||
p := &packages.Package{}
|
||||
p.PkgPath = pkgImportPath
|
||||
p.Fset = fset
|
||||
for _, pkg := range pkgMap {
|
||||
p.Name = pkg.Name
|
||||
s.log.Info("Found package", "pkg.Name", pkg.Name, "p.Name", p.PkgPath)
|
||||
for filename, astFile := range pkg.Files {
|
||||
p.Syntax = append(p.Syntax, astFile)
|
||||
p.GoFiles = append(p.GoFiles, filename)
|
||||
}
|
||||
}
|
||||
s.packageList = append(s.packageList, p)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// This function is used to populate a map so that we can lookup controller embedded types in order to determine
|
||||
// if a Struct inherits from from revel.Controller
|
||||
func (s *SourceProcessor) addImportMap() (err error) {
|
||||
s.importMap = map[string]string{}
|
||||
s.packageMap = map[string]string{}
|
||||
for _, p := range s.packageList {
|
||||
|
||||
if len(p.Errors) > 0 {
|
||||
// Generate a compile error
|
||||
for _, e := range p.Errors {
|
||||
s.log.Info("While reading packages encountered import error ignoring ", "PkgPath", p.PkgPath, "error", e)
|
||||
}
|
||||
}
|
||||
for _, tree := range p.Syntax {
|
||||
s.importMap[tree.Name.Name] = p.PkgPath
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SourceProcessor) addSourceInfo() (err error) {
|
||||
for _, p := range s.packageList {
|
||||
if sourceInfo := s.sourceInfoProcessor.processPackage(p); sourceInfo != nil {
|
||||
if s.sourceInfo != nil {
|
||||
s.sourceInfo.Merge(sourceInfo)
|
||||
} else {
|
||||
s.sourceInfo = sourceInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
151
app/cmd/revel.go
Normal file
151
app/cmd/revel.go
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The command line tool for running Revel apps.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
|
||||
"github.com/agtorre/gocolorize"
|
||||
"github.com/revel/cmd/logger"
|
||||
"github.com/revel/cmd/model"
|
||||
"github.com/revel/cmd/utils"
|
||||
"bytes"
|
||||
)
|
||||
|
||||
const (
|
||||
// RevelCmdImportPath Revel framework cmd tool import path
|
||||
RevelCmdImportPath = "github.com/revel/cmd"
|
||||
|
||||
// RevelCmdImportPath Revel framework cmd tool import path
|
||||
RevelSkeletonsImportPath = "github.com/revel/skeletons"
|
||||
|
||||
// DefaultRunMode for revel's application
|
||||
DefaultRunMode = "dev"
|
||||
)
|
||||
|
||||
// Command structure cribbed from the genius organization of the "go" command.
|
||||
type Command struct {
|
||||
UpdateConfig func(c *model.CommandConfig, args []string) bool
|
||||
RunWith func(c *model.CommandConfig) error
|
||||
UsageLine, Short, Long string
|
||||
}
|
||||
|
||||
// Name returns command name from usage line
|
||||
func (cmd *Command) Name() string {
|
||||
name := cmd.UsageLine
|
||||
i := strings.Index(name, " ")
|
||||
if i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// The commands
|
||||
var Commands = []*Command{
|
||||
nil, // Safety net, prevent missing index from running
|
||||
|
||||
// 只改了这个
|
||||
nil,
|
||||
nil,
|
||||
cmdBuild,
|
||||
}
|
||||
|
||||
func main() {
|
||||
if runtime.GOOS == "windows" {
|
||||
gocolorize.SetPlain(true)
|
||||
}
|
||||
c := &model.CommandConfig{}
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
utils.InitLogger(wd, logger.LvlError)
|
||||
parser := flags.NewParser(c, flags.HelpFlag | flags.PassDoubleDash)
|
||||
if len(os.Args) < 2 {
|
||||
parser.WriteHelp(os.Stdout)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := ParseArgs(c, parser, os.Args[1:]); err != nil {
|
||||
fmt.Fprint(os.Stderr, err.Error() + "\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Switch based on the verbose flag
|
||||
if len(c.Verbose) > 1 {
|
||||
utils.InitLogger(wd, logger.LvlDebug)
|
||||
} else if len(c.Verbose) > 0 {
|
||||
utils.InitLogger(wd, logger.LvlInfo)
|
||||
} else {
|
||||
utils.InitLogger(wd, logger.LvlWarn)
|
||||
}
|
||||
|
||||
// Setup package resolver
|
||||
c.InitPackageResolver()
|
||||
|
||||
if err := c.UpdateImportPath(); err != nil {
|
||||
utils.Logger.Error(err.Error())
|
||||
parser.WriteHelp(os.Stdout)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
command := Commands[c.Index]
|
||||
println("Revel executing:", command.Short)
|
||||
|
||||
if err := command.RunWith(c); err != nil {
|
||||
utils.Logger.Error("Unable to execute", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the arguments passed into the model.CommandConfig
|
||||
func ParseArgs(c *model.CommandConfig, parser *flags.Parser, args []string) (err error) {
|
||||
var extraArgs []string
|
||||
if ini := flag.String("ini", "none", ""); *ini != "none" {
|
||||
if err = flags.NewIniParser(parser).ParseFile(*ini); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if extraArgs, err = parser.ParseArgs(args); err != nil {
|
||||
return
|
||||
} else {
|
||||
switch parser.Active.Name {
|
||||
case "new":
|
||||
c.Index = model.NEW
|
||||
case "run":
|
||||
c.Index = model.RUN
|
||||
case "build":
|
||||
c.Index = model.BUILD
|
||||
case "package":
|
||||
c.Index = model.PACKAGE
|
||||
case "clean":
|
||||
c.Index = model.CLEAN
|
||||
case "test":
|
||||
c.Index = model.TEST
|
||||
case "version":
|
||||
c.Index = model.VERSION
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !Commands[c.Index].UpdateConfig(c, extraArgs) {
|
||||
buffer := &bytes.Buffer{}
|
||||
parser.WriteHelp(buffer)
|
||||
err = fmt.Errorf("Invalid command line arguements %v\n%s", extraArgs, buffer.String())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func (c *BaseController) Message(message string, args ...interface{}) (value str
|
||||
|
||||
func (c BaseController) GetUserId() string {
|
||||
if userId, ok := c.Session["UserId"]; ok {
|
||||
return userId
|
||||
return userId.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -47,51 +47,30 @@ func (c BaseController) GetObjectUserId() bson.ObjectId {
|
||||
|
||||
func (c BaseController) GetEmail() string {
|
||||
if email, ok := c.Session["Email"]; ok {
|
||||
return email
|
||||
return email.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c BaseController) GetUsername() string {
|
||||
if email, ok := c.Session["Username"]; ok {
|
||||
return email
|
||||
return email.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 得到用户信息
|
||||
func (c BaseController) GetUserInfo() info.User {
|
||||
if userId, ok := c.Session["UserId"]; ok && userId != "" {
|
||||
userId := c.GetUserId()
|
||||
if userId != "" {
|
||||
return userService.GetUserInfo(userId)
|
||||
/*
|
||||
notebookWidth, _ := strconv.Atoi(c.Session["NotebookWidth"])
|
||||
noteListWidth, _ := strconv.Atoi(c.Session["NoteListWidth"])
|
||||
mdEditorWidth, _ := strconv.Atoi(c.Session["MdEditorWidth"])
|
||||
LogJ(c.Session)
|
||||
user := info.User{UserId: bson.ObjectIdHex(userId),
|
||||
Email: c.Session["Email"],
|
||||
Logo: c.Session["Logo"],
|
||||
Username: c.Session["Username"],
|
||||
UsernameRaw: c.Session["UsernameRaw"],
|
||||
Theme: c.Session["Theme"],
|
||||
NotebookWidth: notebookWidth,
|
||||
NoteListWidth: noteListWidth,
|
||||
MdEditorWidth: mdEditorWidth,
|
||||
}
|
||||
if c.Session["Verified"] == "1" {
|
||||
user.Verified = true
|
||||
}
|
||||
if c.Session["LeftIsMin"] == "1" {
|
||||
user.LeftIsMin = true
|
||||
}
|
||||
return user
|
||||
*/
|
||||
}
|
||||
return info.User{}
|
||||
}
|
||||
|
||||
func (c BaseController) GetUserAndBlogUrl() info.UserAndBlogUrl {
|
||||
if userId, ok := c.Session["UserId"]; ok && userId != "" {
|
||||
userId := c.GetUserId()
|
||||
if userId != "" {
|
||||
return userService.GetUserAndBlogUrl(userId)
|
||||
}
|
||||
return info.UserAndBlogUrl{}
|
||||
@@ -103,7 +82,7 @@ func (c BaseController) GetSession(key string) string {
|
||||
if !ok {
|
||||
v = ""
|
||||
}
|
||||
return v
|
||||
return v.(string)
|
||||
}
|
||||
func (c BaseController) SetSession(userInfo info.User) {
|
||||
if userInfo.UserId.Hex() != "" {
|
||||
|
||||
@@ -110,7 +110,7 @@ func (c Blog) setPreviewUrl() {
|
||||
if username != "" {
|
||||
userIdOrEmail = username
|
||||
}
|
||||
themeId := c.Session["themeId"]
|
||||
themeId := c.GetSession("themeId")
|
||||
theme := themeService.GetTheme(userId, themeId)
|
||||
|
||||
// siteUrl := configService.GetSiteUrl()
|
||||
|
||||
@@ -34,7 +34,7 @@ func (c Captcha) Get() revel.Result {
|
||||
out := io.Writer(c.Response.GetWriter())
|
||||
image.WriteTo(out)
|
||||
|
||||
sessionId := c.Session["_ID"]
|
||||
sessionId := c.GetSession("_ID")
|
||||
// LogJ(c.Session)
|
||||
// Log("------")
|
||||
// Log(str)
|
||||
|
||||
@@ -25,7 +25,7 @@ func (c Preview) getPreviewThemeAbsolutePath(themeId string) bool {
|
||||
if themeId != "" {
|
||||
c.Session["themeId"] = themeId // 存到session中, 下次的url就不能带了, 待优化, 有时会取不到
|
||||
} else {
|
||||
themeId = c.Session["themeId"] // 直接从session中获取
|
||||
themeId = c.GetSession("themeId") // 直接从session中获取
|
||||
}
|
||||
if themeId == "" {
|
||||
return false
|
||||
|
||||
@@ -83,7 +83,7 @@ func AuthInterceptor(c *revel.Controller) revel.Result {
|
||||
|
||||
// 验证是否已登录
|
||||
// 必须是管理员
|
||||
if username, ok := c.Session["Username"]; ok && username == configService.GetAdminUsername() {
|
||||
if username, ok := c.Session["Username"]; ok && username.(string) == configService.GetAdminUsername() {
|
||||
return nil // 已登录
|
||||
}
|
||||
|
||||
|
||||
@@ -23,18 +23,18 @@ type ApiBaseContrller struct {
|
||||
|
||||
// 得到token, 这个token是在AuthInterceptor设到Session中的
|
||||
func (c ApiBaseContrller) getToken() string {
|
||||
return c.Session["_token"]
|
||||
return c.GetSession("_token")
|
||||
}
|
||||
|
||||
// userId
|
||||
// _userId是在AuthInterceptor设置的
|
||||
func (c ApiBaseContrller) getUserId() string {
|
||||
return c.Session["_userId"]
|
||||
return c.GetSession("_userId")
|
||||
}
|
||||
|
||||
// 得到用户信息
|
||||
func (c ApiBaseContrller) getUserInfo() info.User {
|
||||
userId := c.Session["_userId"]
|
||||
userId := c.GetSession("_userId")
|
||||
if userId == "" {
|
||||
return info.User{}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,10 @@ func AuthInterceptor(c *revel.Controller) revel.Result {
|
||||
if noToken && userId == "" {
|
||||
// 从session中获取, api/file/getImage, api/file/getAttach, api/file/getAllAttach
|
||||
// 客户端
|
||||
userId, _ = c.Session["UserId"]
|
||||
userIdI, _ := c.Session["UserId"]
|
||||
if userIdI != nil {
|
||||
userId = userIdI.(string)
|
||||
}
|
||||
}
|
||||
c.Session["_userId"] = userId
|
||||
|
||||
|
||||
@@ -76,6 +76,11 @@ func Init(url, dbname string) {
|
||||
// get dbname from urlEnv
|
||||
urls := strings.Split(url, "/")
|
||||
dbname = urls[len(urls)-1]
|
||||
|
||||
if strings.Contains(dbname, "?") {
|
||||
urls = strings.Split(dbname, "?")
|
||||
dbname = urls[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
if dbname == "" {
|
||||
|
||||
@@ -91,7 +91,7 @@ func RouterFilter(c *revel.Controller, fc []revel.Filter) {
|
||||
arg := c.MethodType.Args[i]
|
||||
c.Params.Fixed.Set(arg.Name, value)
|
||||
} else {
|
||||
revel.WARN.Println("Too many parameters to", route.Action, "trying to add", value)
|
||||
// revel.WARN.Println("Too many parameters to", route.Action, "trying to add", value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Compress and combine js files
|
||||
@@ -1,159 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
// "time"
|
||||
)
|
||||
|
||||
/*
|
||||
用golang exec 总是说找不到uglifyjs命令, 需要全部路径
|
||||
而且node, npm要在/usr/bin下, 已建ln
|
||||
*/
|
||||
|
||||
/*
|
||||
<script src="js/jquery-1.9.0.min.js"></script>
|
||||
|
||||
<!-- 以后将所有的js压缩合并成一个文件 -->
|
||||
<script src="js/jquery-cookie.js"></script>
|
||||
<script src="js/bootstrap.js"></script>
|
||||
<script type="text/javascript" src="tinymce/tinymce.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/app/note.js"></script>
|
||||
<script src="js/app/tag.js"></script>
|
||||
<script src="js/app/notebook.js"></script>
|
||||
<script src="js/app/share.js"></script>
|
||||
<script src="js/object_id.js"></script>
|
||||
<script type="text/javascript" src="js/ZeroClipboard/ZeroClipboard.js"></script>
|
||||
*/
|
||||
|
||||
//var jss = []string{"js/jquery-cookie", "js/bootstrap"}
|
||||
var jss = []string{"js/jquery-cookie", "js/bootstrap",
|
||||
"js/common", "js/app/note", "js/app/tag", "js/app/notebook", "js/app/share",
|
||||
"js/object_id", "js/ZeroClipboard/ZeroClipboard"}
|
||||
|
||||
var base1 = "/Users/life/Documents/Go/package2/src/github.com/leanote/leanote/"
|
||||
var base = "/Users/life/Documents/Go/package2/src/github.com/leanote/leanote/public/"
|
||||
var cmdPath = "/usr/local/bin/uglifyjs"
|
||||
|
||||
func cmdError(err error) {
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Fprintf(os.Stderr, "The command failed to perform: %s (Command: %s, Arguments: %s)", err, "", "")
|
||||
} else {
|
||||
fmt.Println("OK")
|
||||
}
|
||||
}
|
||||
|
||||
// filename没有扩展名
|
||||
func compressJs(filename string) {
|
||||
source := base + filename + ".js"
|
||||
to := base + filename + "-min.js"
|
||||
cmd := exec.Command(cmdPath, source, "-o", to)
|
||||
_, err := cmd.CombinedOutput()
|
||||
fmt.Println(source)
|
||||
cmdError(err)
|
||||
}
|
||||
|
||||
func combineJs() {
|
||||
// 生成一个总文件
|
||||
cmd := exec.Command("rm", base+"js/all.js")
|
||||
_, err := cmd.CombinedOutput()
|
||||
cmdError(err)
|
||||
|
||||
for _, js := range jss {
|
||||
to := base + js + "-min.js"
|
||||
fmt.Println(to)
|
||||
compressJs(js)
|
||||
|
||||
// 每个压缩后的文件放入之
|
||||
cmd2 := exec.Command("/bin/sh", "-c", "cat "+to+" >> "+base+"js/all.js")
|
||||
_, err := cmd2.CombinedOutput()
|
||||
cmdError(err)
|
||||
cmd2 = exec.Command("/bin/sh", "-c", "cat \n >> "+base+"js/all.js")
|
||||
_, err = cmd2.CombinedOutput()
|
||||
cmdError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 改note-dev->note
|
||||
func dev() {
|
||||
// 即替换note.js->note-min.js
|
||||
m := map[string]string{"tinymce.dev.js": "tinymce.min.js",
|
||||
"tinymce.js": "tinymce.min.js",
|
||||
"jquery.ztree.all-3.5.js": "jquery.ztree.all-3.5-min.js",
|
||||
"note.js": "note-min.js",
|
||||
"app.js": "app-min.js",
|
||||
"page.js": "page-min.js",
|
||||
"common.js": "common-min.js",
|
||||
"notebook.js": "notebook-min.js",
|
||||
"share.js": "share-min.js",
|
||||
"tag.js": "tag-min.js",
|
||||
"jquery.slimscroll.js": "jquery.slimscroll-min.js",
|
||||
"jquery.contextmenu.js": "jquery.contextmenu-min.js",
|
||||
"editor/editor.js": "editor/editor-min.js",
|
||||
"/public/mdeditor/editor/scrollLink.js": "/public/mdeditor/editor/scrollLink-min.js",
|
||||
"console.log(o);": "",
|
||||
}
|
||||
path := base1 + "/src/views/note/note-dev.html"
|
||||
target := base1 + "/src/views/note/note.html"
|
||||
|
||||
bs, _ := ioutil.ReadFile(path)
|
||||
content := string(bs)
|
||||
print(content)
|
||||
for key, value := range m {
|
||||
content = strings.Replace(content, key, value, -1)
|
||||
}
|
||||
|
||||
// var time = time.Now().Unix() % 1000
|
||||
|
||||
// content = strings.Replace(content, "-min.js", fmt.Sprintf("-min.js?r=%d", time), -1)
|
||||
// content = strings.Replace(content, "default{{end}}.css", fmt.Sprintf("default{{end}}.css?r=%d", time), 1)
|
||||
// content = strings.Replace(content, "writting-overwrite.css", fmt.Sprintf("writting-overwrite.css?r=%d", time), 1)
|
||||
|
||||
ioutil.WriteFile(target, []byte(content), os.ModeAppend)
|
||||
}
|
||||
|
||||
// 压缩js成一块
|
||||
func tinymce() {
|
||||
// cmdStr := "node_modules/jake/bin/cli.js minify bundle[themes:modern,plugins:table,paste,advlist,autolink,link,image,lists,charmap,hr,searchreplace,visualblocks,visualchars,code,nav,tabfocus,contextmenu,directionality,codemirror,codesyntax,textcolor,fullpage]"
|
||||
// cmd := exec.Command("/Users/life/Documents/eclipse-workspace/go/leanote_release/tinymce-master/node_modules/jake/bin/cli.js", "minify", "bundle[themes:modern,plugins:table,paste,advlist,autolink,link,image,lists,charmap,hr,searchreplace,visualblocks,visualchars,code,nav,tabfocus,contextmenu,directionality,codemirror,codesyntax,textcolor,fullpage]")
|
||||
cmd := exec.Command("/bin/sh", "-c", "grunt minify")
|
||||
cmd.Dir = base + "/tinymce_4.1.9"
|
||||
|
||||
fmt.Println("正在build tinymce")
|
||||
|
||||
// 必须要先删除
|
||||
cmd2 := exec.Command("/bin/sh", "-c", "rm "+cmd.Dir+"/js/tinymce/tinymce.dev.js")
|
||||
cmd2.CombinedOutput()
|
||||
cmd2 = exec.Command("/bin/sh", "-c", "rm "+cmd.Dir+"/js/tinymce/tinymce.jquery.dev.js")
|
||||
c, _ := cmd2.CombinedOutput()
|
||||
fmt.Println(string(c))
|
||||
|
||||
c, _ = cmd.CombinedOutput()
|
||||
fmt.Println(string(c))
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 压缩tinymce
|
||||
// tinymce()
|
||||
|
||||
dev()
|
||||
|
||||
// 其它零散的需要压缩的js
|
||||
otherJss := []string{"js/main", "js/app/page", "js/contextmenu/jquery.contextmenu",
|
||||
"js/jquery.ztree.all-3.5",
|
||||
"js/jQuery-slimScroll-1.3.0/jquery.slimscroll",
|
||||
}
|
||||
|
||||
for _, js := range otherJss {
|
||||
compressJs(js)
|
||||
}
|
||||
|
||||
// 先压缩后合并
|
||||
combineJs()
|
||||
|
||||
}
|
||||
@@ -616,7 +616,7 @@ func (this *NoteService) UpdateNoteContent(updatedUserId, noteId, content, abstr
|
||||
return false, "conflict", 0
|
||||
}
|
||||
afterUsn = userService.IncrUsn(userId)
|
||||
db.UpdateByIdAndUserIdField(db.Notes, noteId, userId, "Usn", usn)
|
||||
db.UpdateByIdAndUserIdField(db.Notes, noteId, userId, "Usn", afterUsn)
|
||||
}
|
||||
|
||||
if db.UpdateByIdAndUserIdMap(db.NoteContents, noteId, userId, data) {
|
||||
|
||||
150
bin/release.sh
150
bin/release.sh
@@ -1,150 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# release leanote
|
||||
|
||||
# 当前路径
|
||||
SP=$(cd "$(dirname "$0")"; pwd)
|
||||
|
||||
# tmp path to store leanote release files
|
||||
tmp="/Users/life/Desktop/leanote_release"
|
||||
|
||||
# version
|
||||
V="v2.6.1"
|
||||
|
||||
##=================================
|
||||
# 1. 先build 成 3个平台, 2种bit = 6种
|
||||
##=================================
|
||||
|
||||
# cd /Documents/Go/package2/src/github.com/leanote/leanote/bin
|
||||
# GOOS=darwin GOARCH=amd64 go build -o leanote-darwin-amd64 ../app/tmp
|
||||
|
||||
cd $SP
|
||||
|
||||
# $1 = darwin, linux
|
||||
# $2 = amd64
|
||||
function build()
|
||||
{
|
||||
echo build-$1-$2
|
||||
if [ $1 = "linux" -o $1 = "darwin" ]
|
||||
then
|
||||
suffix=""
|
||||
else
|
||||
suffix=".exe"
|
||||
fi
|
||||
|
||||
GOOS=$1 GOARCH=$2 go build -o leanote-$1-$2$suffix github.com/leanote/leanote/app/tmp
|
||||
}
|
||||
|
||||
build "linux" "386";
|
||||
build "linux" "amd64";
|
||||
build "linux" "arm";
|
||||
|
||||
build "windows" "386";
|
||||
build "windows" "amd64";
|
||||
|
||||
build "darwin" "amd64";
|
||||
|
||||
|
||||
##======================
|
||||
# 2. release目录准备工作
|
||||
##======================
|
||||
rm -rf $tmp/leanote
|
||||
mkdir -p $tmp/leanote/app
|
||||
mkdir -p $tmp/leanote/conf
|
||||
mkdir -p $tmp/leanote/bin
|
||||
|
||||
##==================
|
||||
# 3. 复制
|
||||
##==================
|
||||
|
||||
cd $SP
|
||||
cd ../
|
||||
|
||||
# bin
|
||||
cp -r ./bin/src $tmp/leanote/bin/
|
||||
cp ./bin/run.sh $tmp/leanote/bin/
|
||||
cp ./bin/run.bat $tmp/leanote/bin/
|
||||
|
||||
# views
|
||||
cp -r ./app/views $tmp/leanote/app
|
||||
# 可不要, 源码
|
||||
#cp -r ./app/service $tmp/leanote/app/service
|
||||
#cp -r ./app/controllers $tmp/leanote/app/controllers
|
||||
#cp -r ./app/db $tmp/leanote/app/db
|
||||
#cp -r ./app/info $tmp/leanote/app/info
|
||||
#cp -r ./app/lea $tmp/leanote/app/lea
|
||||
|
||||
# conf
|
||||
cp ./conf/app.conf $tmp/leanote/conf/app.conf
|
||||
cp ./conf/routes $tmp/leanote/conf/routes
|
||||
# 处理app.conf, 还原配置
|
||||
cat $tmp/leanote/conf/app.conf | sed 's/db.dbname=leanote.*#/db.dbname=leanote #/' > $tmp/leanote/conf/app.conf2 # 不能直接覆盖
|
||||
rm $tmp/leanote/conf/app.conf
|
||||
mv $tmp/leanote/conf/app.conf2 $tmp/leanote/conf/app.conf
|
||||
|
||||
# others
|
||||
cp -r ./messages ./public ./mongodb_backup $tmp/leanote/
|
||||
|
||||
# delete some files
|
||||
rm -r $tmp/leanote/public/tinymce/classes
|
||||
rm -r $tmp/leanote/public/upload
|
||||
mkdir $tmp/leanote/public/upload
|
||||
rm -r $tmp/leanote/public/.codekit-cache
|
||||
rm $tmp/leanote/public/.DS_Store
|
||||
rm $tmp/leanote/public/config.codekit
|
||||
|
||||
# make link
|
||||
# cd $tmp/leanote/bin
|
||||
# ln -s ../../../../ ./src/github.com/leanote/leanote
|
||||
|
||||
# archieve
|
||||
# << 'BLOCK
|
||||
|
||||
##===========
|
||||
# 4. 打包
|
||||
##===========
|
||||
# $1 = linux
|
||||
# $2 = 386, amd64
|
||||
|
||||
# 创建一个$V的目录存放之
|
||||
rm -rf $tmp/$V
|
||||
mkdir $tmp/$V
|
||||
|
||||
function tarRelease()
|
||||
{
|
||||
echo tar-$1-$2
|
||||
cd $SP
|
||||
cd ../
|
||||
rm $tmp/leanote/bin/leanote-* # 删除之前的bin文件
|
||||
rm $tmp/leanote/bin/run* # 删除之前的run.sh 或 run.bat
|
||||
|
||||
if [ $1 = "linux" -o $1 = "darwin" ]
|
||||
then
|
||||
suffix=""
|
||||
if [ $2 = "arm" ]
|
||||
then
|
||||
cp ./bin/run-arm.sh $tmp/leanote/bin/run.sh
|
||||
else
|
||||
cp ./bin/run-$1-$2.sh $tmp/leanote/bin/run.sh
|
||||
fi
|
||||
else
|
||||
cp ./bin/run.bat $tmp/leanote/bin/
|
||||
suffix=".exe"
|
||||
fi
|
||||
|
||||
cp ./bin/leanote-$1-$2$suffix $tmp/leanote/bin/
|
||||
cd $tmp
|
||||
tar -cf $tmp/$V/leanote-$1-$2-$V.bin.tar leanote
|
||||
gzip $tmp/$V/leanote-$1-$2-$V.bin.tar
|
||||
}
|
||||
|
||||
tarRelease "linux" "386";
|
||||
tarRelease "linux" "amd64";
|
||||
tarRelease "linux" "arm";
|
||||
|
||||
tarRelease "windows" "386";
|
||||
tarRelease "windows" "amd64";
|
||||
|
||||
tarRelease "darwin" "amd64";
|
||||
|
||||
# BLOCK'
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||
|
||||
# set link
|
||||
|
||||
path="$SCRIPTPATH/src/github.com/leanote"
|
||||
if [ ! -d "$path" ]; then
|
||||
mkdir -p "$path"
|
||||
fi
|
||||
rm -rf $SCRIPTPATH/src/github.com/leanote/leanote # 先删除
|
||||
ln -s ../../../../ $SCRIPTPATH/src/github.com/leanote/leanote
|
||||
|
||||
# set GOPATH
|
||||
export GOPATH=$SCRIPTPATH
|
||||
|
||||
script="$SCRIPTPATH/leanote-linux-arm"
|
||||
chmod 777 $script
|
||||
$script -importPath github.com/leanote/leanote
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||
|
||||
# set link
|
||||
|
||||
path="$SCRIPTPATH/src/github.com/leanote"
|
||||
if [ ! -d "$path" ]; then
|
||||
mkdir -p "$path"
|
||||
fi
|
||||
rm -rf $SCRIPTPATH/src/github.com/leanote/leanote # 先删除
|
||||
ln -s ../../../../ $SCRIPTPATH/src/github.com/leanote/leanote
|
||||
|
||||
# set GOPATH
|
||||
export GOPATH=$SCRIPTPATH
|
||||
|
||||
script="$SCRIPTPATH/leanote-darwin-amd64"
|
||||
chmod 777 $script
|
||||
$script -importPath github.com/leanote/leanote
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||
|
||||
# set link
|
||||
|
||||
path="$SCRIPTPATH/src/github.com/leanote"
|
||||
if [ ! -d "$path" ]; then
|
||||
mkdir -p "$path"
|
||||
fi
|
||||
rm -rf $SCRIPTPATH/src/github.com/leanote/leanote # 先删除
|
||||
ln -s ../../../../ $SCRIPTPATH/src/github.com/leanote/leanote
|
||||
|
||||
# set GOPATH
|
||||
export GOPATH=$SCRIPTPATH
|
||||
|
||||
script="$SCRIPTPATH/leanote-linux-386"
|
||||
chmod 777 $script
|
||||
$script -importPath github.com/leanote/leanote
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||
|
||||
# set link
|
||||
|
||||
path="$SCRIPTPATH/src/github.com/leanote"
|
||||
if [ ! -d "$path" ]; then
|
||||
mkdir -p "$path"
|
||||
fi
|
||||
rm -rf $SCRIPTPATH/src/github.com/leanote/leanote # 先删除
|
||||
ln -s ../../../../ $SCRIPTPATH/src/github.com/leanote/leanote
|
||||
|
||||
# set GOPATH
|
||||
export GOPATH=$SCRIPTPATH
|
||||
|
||||
script="$SCRIPTPATH/leanote-linux-amd64"
|
||||
chmod 777 $script
|
||||
$script -importPath github.com/leanote/leanote
|
||||
23
bin/run.bat
23
bin/run.bat
@@ -1,23 +0,0 @@
|
||||
@echo off
|
||||
|
||||
cd..
|
||||
set SCRIPTPATH=%cd%
|
||||
|
||||
: top src directory
|
||||
set leanotePath="%SCRIPTPATH%\bin\src\github.com\leanote"
|
||||
|
||||
if not exist "%leanotePath%" mkdir "%leanotePath%"
|
||||
|
||||
: create software link
|
||||
if exist "%leanotePath%\leanote" del /Q "%leanotePath%\leanote"
|
||||
mklink /D "%leanotePath%\leanote" %SCRIPTPATH%
|
||||
|
||||
: set GOPATH
|
||||
set GOPATH="%SCRIPTPATH%\bin"
|
||||
|
||||
: run
|
||||
if %processor_architecture%==x86 (
|
||||
"%SCRIPTPATH%\bin\leanote-windows-386.exe" -importPath github.com/leanote/leanote
|
||||
) else (
|
||||
"%SCRIPTPATH%\bin\leanote-windows-amd64.exe" -importPath github.com/leanote/leanote
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
modules
|
||||
=======
|
||||
|
||||
Set of officially supported modules for Revel applications
|
||||
|
||||
### Caution
|
||||
this is a work in progress
|
||||
@@ -1,6 +0,0 @@
|
||||
modules/auth
|
||||
===============
|
||||
|
||||
Basic user/auth module
|
||||
|
||||
This should be modeled after [flask-security](https://github.com/mattupstate/flask-security)
|
||||
@@ -1,78 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
// "errors"
|
||||
)
|
||||
|
||||
var (
|
||||
Store StorageDriver
|
||||
)
|
||||
|
||||
// Store = gormauth.NewGormAuthDriver()
|
||||
|
||||
type UserAuth interface {
|
||||
// getters/setters implemented by the app-level model
|
||||
UserId() string
|
||||
Secret() string
|
||||
HashedSecret() string
|
||||
SetHashedSecret(string)
|
||||
|
||||
SecretDriver
|
||||
// // implemented by secret driver
|
||||
// Authenticate() (bool, error)
|
||||
}
|
||||
|
||||
type SecretDriver interface {
|
||||
Authenticate() (bool, error)
|
||||
HashSecret(args ...interface{}) (string, error)
|
||||
|
||||
// stuff for documentation
|
||||
// UserContext is expected in these?
|
||||
|
||||
// Secret expects 0 or non-0 arguments
|
||||
// When no parameter is passed, it acts as a getter
|
||||
// When one or more parameters are passed, it acts as a setter
|
||||
// A driver should specify the expected arguments and their meanings
|
||||
|
||||
// Register()
|
||||
// Login()
|
||||
// Logout()
|
||||
}
|
||||
|
||||
type StorageDriver interface {
|
||||
Save(user interface{}) error
|
||||
// Load should take a partially filled struct
|
||||
// (with values needed to look up)
|
||||
// and fills in the rest
|
||||
Load(user interface{}) error
|
||||
}
|
||||
|
||||
// func init() {
|
||||
// // auth.Store = gorm...
|
||||
// }
|
||||
|
||||
// func (c App) Login(email, password string) {
|
||||
|
||||
// u := User {
|
||||
// Email ...
|
||||
// }
|
||||
|
||||
// good, err := auth.Authenticate(email, password)
|
||||
|
||||
// user, err := user_info.GetUserByEmail(email)
|
||||
// }
|
||||
|
||||
// Bycrypt Authenticate() expects a single string argument of the plaintext password
|
||||
// It returns true on success and false if error or password mismatch
|
||||
// func Authenticate(attemptedUser UserAuth) (bool, error) {
|
||||
// // check user in Store
|
||||
// loadedUser, err := Store.Load(attemptedUser.UserId())
|
||||
// if err != nil {
|
||||
// return false, errors.New("User Not Found")
|
||||
// }
|
||||
|
||||
// loadedUser.Authenticate(attemptedUser.Secret())
|
||||
|
||||
// // successfully authenticated
|
||||
// return true, nil
|
||||
// }
|
||||
@@ -1,134 +0,0 @@
|
||||
package auth_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/revel/modules/auth"
|
||||
"github.com/revel/modules/auth/driver/secret"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
email string
|
||||
password string
|
||||
hashpass string
|
||||
|
||||
secret.BcryptAuth // SecurityDriver for testing
|
||||
}
|
||||
|
||||
func NewUser(email, pass string) *User {
|
||||
u := &User{
|
||||
email: email,
|
||||
password: pass,
|
||||
}
|
||||
u.UserContext = u
|
||||
return u
|
||||
}
|
||||
|
||||
func (self *User) UserId() string {
|
||||
return self.email
|
||||
}
|
||||
|
||||
func (self *User) Secret() string {
|
||||
return self.password
|
||||
}
|
||||
|
||||
func (self *User) HashedSecret() string {
|
||||
return self.hashpass
|
||||
}
|
||||
|
||||
func (self *User) SetHashedSecret(hpass string) {
|
||||
self.hashpass = hpass
|
||||
}
|
||||
|
||||
// func (self *User) Load() string
|
||||
|
||||
type TestStore struct {
|
||||
data map[string]string
|
||||
}
|
||||
|
||||
func (self *TestStore) Save(user interface{}) error {
|
||||
u, ok := user.(*User)
|
||||
if !ok {
|
||||
return errors.New("TestStore.Save() expected arg of type User")
|
||||
}
|
||||
|
||||
hPass, err := u.HashSecret(u.Secret())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.data[u.UserId()] = hPass
|
||||
|
||||
return nil
|
||||
}
|
||||
func (self *TestStore) Load(user interface{}) error {
|
||||
u, ok := user.(*User)
|
||||
if !ok {
|
||||
return errors.New("TestStore.Load() expected arg of type User")
|
||||
}
|
||||
|
||||
hpass, ok := self.data[u.UserId()]
|
||||
if !ok {
|
||||
return errors.New("Record Not Found")
|
||||
}
|
||||
u.SetHashedSecret(hpass)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPasswordHash(t *testing.T) {
|
||||
auth.Store = &TestStore{
|
||||
data: make(map[string]string),
|
||||
}
|
||||
u := NewUser("demo@domain.com", "demopass")
|
||||
fail := NewUser("demo@domain.com", "")
|
||||
|
||||
var err error
|
||||
u.hashpass, err = u.HashSecret(u.password)
|
||||
if err != nil {
|
||||
t.Errorf("Should have hashed password, get error: %v\n", err)
|
||||
}
|
||||
fail.hashpass, err = fail.HashSecret(fail.password)
|
||||
if err == nil {
|
||||
t.Errorf("Should have failed hashing\n")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticate(t *testing.T) {
|
||||
auth.Store = &TestStore{
|
||||
data: make(map[string]string),
|
||||
}
|
||||
|
||||
// user registered a long time ago
|
||||
u := NewUser("demo@domain.com", "demopass")
|
||||
err := auth.Store.Save(u)
|
||||
if err != nil {
|
||||
t.Errorf("Should have saved user: %v", err)
|
||||
}
|
||||
|
||||
// users now logging in
|
||||
pass := NewUser("demo@domain.com", "demopass")
|
||||
fail := NewUser("demo@domain.com", "invalid")
|
||||
|
||||
// valid user is now trying to login
|
||||
// check user in DB
|
||||
err = auth.Store.Load(pass)
|
||||
if err != nil {
|
||||
t.Errorf("Should have loaded pass user: %v\n", err)
|
||||
}
|
||||
// check credentials
|
||||
ok, err := pass.Authenticate()
|
||||
if !ok || err != nil {
|
||||
t.Errorf("Should have authenticated user")
|
||||
}
|
||||
|
||||
// invalid user is now trying to login
|
||||
err = auth.Store.Load(fail)
|
||||
if err != nil {
|
||||
t.Errorf("Should have loaded fail user")
|
||||
}
|
||||
// this should fail
|
||||
ok, err = fail.Authenticate()
|
||||
if ok || err != nil {
|
||||
t.Errorf("Should have failed to authenticate user: %v\n", err)
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/* A basic user authentication module for Revel
|
||||
|
||||
list of concerns:
|
||||
- Separating out the interface and driver
|
||||
- Removing DB/Storage dependency
|
||||
- UUID as default identifier?
|
||||
- how to deal with password/secret or generally, method of authorization
|
||||
- default {views,controllers,routes} for register/login/logut ?
|
||||
- reset password in most basic ?
|
||||
- activation (and other features) in a second / more sophisticated driver
|
||||
- filter for checking that user is authenticated
|
||||
|
||||
|
||||
I think a driver is made up of 2 parts
|
||||
data prep and data storage
|
||||
register and password reset are part of data prep
|
||||
as is the auth hash method
|
||||
they don't care how the data is stored
|
||||
|
||||
then there is the data store
|
||||
|
||||
perhaps each auth user model should instantiate 2 drivers instead of 1?
|
||||
one for data prep components and one for storage
|
||||
so the security driver and the storage driver
|
||||
|
||||
*/
|
||||
package auth
|
||||
@@ -1,73 +0,0 @@
|
||||
package secret
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"github.com/revel/modules/auth"
|
||||
)
|
||||
|
||||
// example implementation of a Revel auth security driver
|
||||
// This driver should be embedded into your app-level User model
|
||||
// It expects your User model to have `Password` and `HashedPassword` string fields
|
||||
//
|
||||
// Your User model also needs to set itself as the UserContext for the BcryptAuth driver
|
||||
//
|
||||
// func NewUser(email, pass string) *User {
|
||||
// u := &User{
|
||||
// email: email,
|
||||
// password: pass,
|
||||
// }
|
||||
// u.UserContext = u
|
||||
// }
|
||||
//
|
||||
type BcryptAuth struct {
|
||||
UserContext auth.UserAuth
|
||||
}
|
||||
|
||||
// Bcrypt Secret() returns the hashed version of the password.
|
||||
// It expects an argument of type string, which is the plain text password
|
||||
func (self *BcryptAuth) HashSecret(args ...interface{}) (string, error) {
|
||||
if auth.Store == nil {
|
||||
return "", errors.New("Auth module StorageDriver not set")
|
||||
}
|
||||
argLen := len(args)
|
||||
if argLen == 0 {
|
||||
// we are getting
|
||||
return string(self.UserContext.HashedSecret()), nil
|
||||
}
|
||||
|
||||
if argLen == 1 {
|
||||
// we are setting
|
||||
password, ok := args[0].(string)
|
||||
if !ok {
|
||||
return "", errors.New("Wrong argument type provided, expected plaintext password as string")
|
||||
}
|
||||
hPass, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
self.UserContext.SetHashedSecret(string(hPass))
|
||||
return self.UserContext.HashedSecret(), nil
|
||||
}
|
||||
|
||||
// bad argument count
|
||||
return "", errors.New("Too many arguments provided, expected one")
|
||||
}
|
||||
|
||||
// Bycrypt Authenticate() expects a single string argument of the plaintext password
|
||||
// It returns true on success and false if error or password mismatch
|
||||
func (self *BcryptAuth) Authenticate() (bool, error) {
|
||||
// check password
|
||||
err := bcrypt.CompareHashAndPassword([]byte(self.UserContext.HashedSecret()), []byte(self.UserContext.Secret()))
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// successfully authenticated
|
||||
return true, nil
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
Mysql Auth driver
|
||||
==================
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Postgresql Auth driver
|
||||
==================
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Sqlite Auth driver
|
||||
==================
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package auth
|
||||
|
||||
// var storageDriver auth.StorageDriver // postgres in example
|
||||
|
||||
// type AuthUserModel struct {
|
||||
// userId string
|
||||
// security *SecurityDriver // bcrypt in example
|
||||
|
||||
// }
|
||||
|
||||
// func (self *AuthUserModel) UserId() string {
|
||||
// return self.userId
|
||||
// }
|
||||
|
||||
// func (self *AuthUserModel) Secret() string {
|
||||
// return self.security.Secret()
|
||||
// }
|
||||
@@ -1,121 +0,0 @@
|
||||
package csrf
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"html/template"
|
||||
"io"
|
||||
"math"
|
||||
"net/url"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
// allowMethods are HTTP methods that do NOT require a token
|
||||
var allowedMethods = map[string]bool{
|
||||
"GET": true,
|
||||
"HEAD": true,
|
||||
"OPTIONS": true,
|
||||
"TRACE": true,
|
||||
}
|
||||
|
||||
func RandomString(length int) (string, error) {
|
||||
buffer := make([]byte, int(math.Ceil(float64(length)/2)))
|
||||
if _, err := io.ReadFull(rand.Reader, buffer); err != nil {
|
||||
return "", nil
|
||||
}
|
||||
str := hex.EncodeToString(buffer)
|
||||
return str[:length], nil
|
||||
}
|
||||
|
||||
func RefreshToken(c *revel.Controller) {
|
||||
token, err := RandomString(64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.Session["csrf_token"] = token
|
||||
}
|
||||
|
||||
// CsrfFilter enables CSRF request token creation and verification.
|
||||
//
|
||||
// Usage:
|
||||
// 1) Add `csrf.CsrfFilter` to the app's filters (it must come after the revel.SessionFilter).
|
||||
// 2) Add CSRF fields to a form with the template tag `{{ csrftoken . }}`. The filter adds a function closure to the `RenderArgs` that can pull out the secret and make the token as-needed, caching the value in the request. Ajax support provided through the `X-CSRFToken` header.
|
||||
func CsrfFilter(c *revel.Controller, fc []revel.Filter) {
|
||||
token, foundToken := c.Session["csrf_token"]
|
||||
|
||||
if !foundToken {
|
||||
RefreshToken(c)
|
||||
}
|
||||
|
||||
referer, refErr := url.Parse(c.Request.Header.Get("Referer"))
|
||||
isSameOrigin := sameOrigin(c.Request.URL, referer)
|
||||
|
||||
// If the Request method isn't in the white listed methods
|
||||
if !allowedMethods[c.Request.Method] && !IsExempt(c) {
|
||||
// Token wasn't present at all
|
||||
if !foundToken {
|
||||
c.Result = c.Forbidden("REVEL CSRF: Session token missing.")
|
||||
return
|
||||
}
|
||||
|
||||
// Referer header is invalid
|
||||
if refErr != nil {
|
||||
c.Result = c.Forbidden("REVEL CSRF: HTTP Referer malformed.")
|
||||
return
|
||||
}
|
||||
|
||||
// Same origin
|
||||
if !isSameOrigin {
|
||||
c.Result = c.Forbidden("REVEL CSRF: Same origin mismatch.")
|
||||
return
|
||||
}
|
||||
|
||||
var requestToken string
|
||||
// First check for token in post data
|
||||
if c.Request.Method == "POST" {
|
||||
requestToken = c.Request.FormValue("csrftoken")
|
||||
}
|
||||
|
||||
// Then check for token in custom headers, as with AJAX
|
||||
if requestToken == "" {
|
||||
requestToken = c.Request.Header.Get("X-CSRFToken")
|
||||
}
|
||||
|
||||
if requestToken == "" || !compareToken(requestToken, token) {
|
||||
c.Result = c.Forbidden("REVEL CSRF: Invalid token.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fc[0](c, fc[1:])
|
||||
|
||||
// Only add token to RenderArgs if the request is: not AJAX, not missing referer header, and is same origin.
|
||||
if c.Request.Header.Get("X-CSRFToken") == "" && isSameOrigin {
|
||||
c.RenderArgs["_csrftoken"] = token
|
||||
}
|
||||
}
|
||||
|
||||
func compareToken(requestToken, token string) bool {
|
||||
// ConstantTimeCompare will panic if the []byte aren't the same length
|
||||
if len(requestToken) != len(token) {
|
||||
return false
|
||||
}
|
||||
return subtle.ConstantTimeCompare([]byte(requestToken), []byte(token)) == 1
|
||||
}
|
||||
|
||||
// Validates same origin policy
|
||||
func sameOrigin(u1, u2 *url.URL) bool {
|
||||
return u1.Scheme == u2.Scheme && u1.Host == u2.Host
|
||||
}
|
||||
|
||||
func init() {
|
||||
revel.TemplateFuncs["csrftoken"] = func(renderArgs map[string]interface{}) template.HTML {
|
||||
if tokenFunc, ok := renderArgs["_csrftoken"]; !ok {
|
||||
panic("REVEL CSRF: _csrftoken missing from RenderArgs.")
|
||||
} else {
|
||||
return template.HTML(tokenFunc.(func() string)())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package csrf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var testFilters = []revel.Filter{
|
||||
CsrfFilter,
|
||||
func(c *revel.Controller, fc []revel.Filter) {
|
||||
c.RenderHtml("{{ csrftoken . }}")
|
||||
},
|
||||
}
|
||||
|
||||
func TestTokenInSession(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
getRequest, _ := http.NewRequest("GET", "http://www.example.com/", nil)
|
||||
c := revel.NewController(revel.NewRequest(getRequest), revel.NewResponse(resp))
|
||||
c.Session = make(revel.Session)
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if _, ok := c.Session["csrf_token"]; !ok {
|
||||
t.Fatal("token should be present in session")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPostWithoutToken(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
postRequest, _ := http.NewRequest("POST", "http://www.example.com/", nil)
|
||||
c := revel.NewController(revel.NewRequest(postRequest), revel.NewResponse(resp))
|
||||
c.Session = make(revel.Session)
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if c.Response.Status != 403 {
|
||||
t.Fatal("post without token should be forbidden")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoReferrer(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
postRequest, _ := http.NewRequest("POST", "http://www.example.com/", nil)
|
||||
|
||||
c := revel.NewController(revel.NewRequest(postRequest), revel.NewResponse(resp))
|
||||
c.Session = make(revel.Session)
|
||||
|
||||
RefreshToken(c)
|
||||
token := c.Session["csrf_token"]
|
||||
|
||||
// make a new request with the token
|
||||
data := url.Values{}
|
||||
data.Set("csrftoken", token)
|
||||
formPostRequest, _ := http.NewRequest("POST", "http://www.example.com/", bytes.NewBufferString(data.Encode()))
|
||||
formPostRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
formPostRequest.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
|
||||
|
||||
// and replace the old request
|
||||
c.Request = revel.NewRequest(formPostRequest)
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if c.Response.Status != 403 {
|
||||
t.Fatal("post without referer should be forbidden")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefererHttps(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
postRequest, _ := http.NewRequest("POST", "http://www.example.com/", nil)
|
||||
c := revel.NewController(revel.NewRequest(postRequest), revel.NewResponse(resp))
|
||||
|
||||
c.Session = make(revel.Session)
|
||||
|
||||
RefreshToken(c)
|
||||
token := c.Session["csrf_token"]
|
||||
|
||||
// make a new request with the token
|
||||
data := url.Values{}
|
||||
data.Set("csrftoken", token)
|
||||
formPostRequest, _ := http.NewRequest("POST", "https://www.example.com/", bytes.NewBufferString(data.Encode()))
|
||||
formPostRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
formPostRequest.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
|
||||
formPostRequest.Header.Add("Referer", "http://www.example.com/")
|
||||
|
||||
// and replace the old request
|
||||
c.Request = revel.NewRequest(formPostRequest)
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if c.Response.Status != 403 {
|
||||
t.Fatal("posts to https should have an https referer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHeaderWithToken(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
postRequest, _ := http.NewRequest("POST", "http://www.example.com/", nil)
|
||||
c := revel.NewController(revel.NewRequest(postRequest), revel.NewResponse(resp))
|
||||
|
||||
c.Session = make(revel.Session)
|
||||
|
||||
RefreshToken(c)
|
||||
token := c.Session["csrf_token"]
|
||||
|
||||
// make a new request with the token
|
||||
formPostRequest, _ := http.NewRequest("POST", "http://www.example.com/", nil)
|
||||
formPostRequest.Header.Add("X-CSRFToken", token)
|
||||
formPostRequest.Header.Add("Referer", "http://www.example.com/")
|
||||
|
||||
// and replace the old request
|
||||
c.Request = revel.NewRequest(formPostRequest)
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if c.Response.Status == 403 {
|
||||
t.Fatal("post with http header token should be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormPostWithToken(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
postRequest, _ := http.NewRequest("POST", "http://www.example.com/", nil)
|
||||
c := revel.NewController(revel.NewRequest(postRequest), revel.NewResponse(resp))
|
||||
|
||||
c.Session = make(revel.Session)
|
||||
|
||||
RefreshToken(c)
|
||||
token := c.Session["csrf_token"]
|
||||
|
||||
// make a new request with the token
|
||||
data := url.Values{}
|
||||
data.Set("csrftoken", token)
|
||||
formPostRequest, _ := http.NewRequest("POST", "http://www.example.com/", bytes.NewBufferString(data.Encode()))
|
||||
formPostRequest.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
formPostRequest.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
|
||||
formPostRequest.Header.Add("Referer", "http://www.example.com/")
|
||||
|
||||
// and replace the old request
|
||||
c.Request = revel.NewRequest(formPostRequest)
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if c.Response.Status == 403 {
|
||||
t.Fatal("form post with token should be allowed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoTokenInArgsWhenCORs(t *testing.T) {
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
getRequest, _ := http.NewRequest("GET", "http://www.example1.com/", nil)
|
||||
getRequest.Header.Add("Referer", "http://www.example2.com/")
|
||||
|
||||
c := revel.NewController(revel.NewRequest(getRequest), revel.NewResponse(resp))
|
||||
c.Session = make(revel.Session)
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if _, ok := c.RenderArgs["_csrftoken"]; ok {
|
||||
t.Fatal("RenderArgs should not contain token when not same origin")
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package csrf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var (
|
||||
exemptPath = make(map[string]bool)
|
||||
exemptAction = make(map[string]bool)
|
||||
)
|
||||
|
||||
func MarkExempt(route string) {
|
||||
if strings.HasPrefix(route, "/") {
|
||||
// e.g. "/controller/action"
|
||||
exemptPath[strings.ToLower(route)] = true
|
||||
} else if routeParts := strings.Split(route, "."); len(routeParts) == 2 {
|
||||
// e.g. "ControllerName.ActionName"
|
||||
exemptAction[route] = true
|
||||
} else {
|
||||
err := fmt.Sprintf("csrf.MarkExempt() received invalid argument \"%v\". Either provide a path prefixed with \"/\" or controller action in the form of \"ControllerName.ActionName\".", route)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func IsExempt(c *revel.Controller) bool {
|
||||
if _, ok := exemptPath[strings.ToLower(c.Request.Request.URL.Path)]; ok {
|
||||
return true
|
||||
} else if _, ok := exemptAction[c.Action]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package csrf
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
func TestExemptPath(t *testing.T) {
|
||||
MarkExempt("/Controller/Action")
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
postRequest, _ := http.NewRequest("POST", "http://www.example.com/Controller/Action", nil)
|
||||
c := revel.NewController(revel.NewRequest(postRequest), revel.NewResponse(resp))
|
||||
c.Session = make(revel.Session)
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if c.Response.Status == 403 {
|
||||
t.Fatal("post to csrf exempt action should pass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExemptPathCaseInsensitive(t *testing.T) {
|
||||
MarkExempt("/Controller/Action")
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
postRequest, _ := http.NewRequest("POST", "http://www.example.com/controller/action", nil)
|
||||
c := revel.NewController(revel.NewRequest(postRequest), revel.NewResponse(resp))
|
||||
c.Session = make(revel.Session)
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if c.Response.Status == 403 {
|
||||
t.Fatal("post to csrf exempt action should pass")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExemptAction(t *testing.T) {
|
||||
MarkExempt("Controller.Action")
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
postRequest, _ := http.NewRequest("POST", "http://www.example.com/Controller/Action", nil)
|
||||
c := revel.NewController(revel.NewRequest(postRequest), revel.NewResponse(resp))
|
||||
c.Session = make(revel.Session)
|
||||
c.Action = "Controller.Action"
|
||||
|
||||
testFilters[0](c, testFilters)
|
||||
|
||||
if c.Response.Status == 403 {
|
||||
t.Fatal("post to csrf exempt action should pass")
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
// This module configures a database connection for the application.
|
||||
//
|
||||
// Developers use this module by importing and calling db.Init().
|
||||
// A "Transactional" controller type is provided as a way to import interceptors
|
||||
// that manage the transaction
|
||||
//
|
||||
// In particular, a transaction is begun before each request and committed on
|
||||
// success. If a panic occurred during the request, the transaction is rolled
|
||||
// back. (The application may also roll the transaction back itself.)
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var (
|
||||
Db *sql.DB
|
||||
Driver string
|
||||
Spec string
|
||||
)
|
||||
|
||||
func Init() {
|
||||
// Read configuration.
|
||||
var found bool
|
||||
if Driver, found = revel.Config.String("db.driver"); !found {
|
||||
revel.ERROR.Fatal("No db.driver found.")
|
||||
}
|
||||
if Spec, found = revel.Config.String("db.spec"); !found {
|
||||
revel.ERROR.Fatal("No db.spec found.")
|
||||
}
|
||||
|
||||
// Open a connection.
|
||||
var err error
|
||||
Db, err = sql.Open(Driver, Spec)
|
||||
if err != nil {
|
||||
revel.ERROR.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
type Transactional struct {
|
||||
*revel.Controller
|
||||
Txn *sql.Tx
|
||||
}
|
||||
|
||||
// Begin a transaction
|
||||
func (c *Transactional) Begin() revel.Result {
|
||||
txn, err := Db.Begin()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.Txn = txn
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rollback if it's still going (must have panicked).
|
||||
func (c *Transactional) Rollback() revel.Result {
|
||||
if c.Txn != nil {
|
||||
if err := c.Txn.Rollback(); err != nil {
|
||||
if err != sql.ErrTxDone {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
c.Txn = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit the transaction.
|
||||
func (c *Transactional) Commit() revel.Result {
|
||||
if c.Txn != nil {
|
||||
if err := c.Txn.Commit(); err != nil {
|
||||
if err != sql.ErrTxDone {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
c.Txn = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
revel.InterceptMethod((*Transactional).Begin, revel.BEFORE)
|
||||
revel.InterceptMethod((*Transactional).Commit, revel.AFTER)
|
||||
revel.InterceptMethod((*Transactional).Rollback, revel.FINALLY)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
"github.com/revel/modules/jobs/app/jobs"
|
||||
"github.com/robfig/cron"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Jobs struct {
|
||||
*revel.Controller
|
||||
}
|
||||
|
||||
func (c Jobs) Status() revel.Result {
|
||||
remoteAddress := c.Request.RemoteAddr
|
||||
if revel.Config.BoolDefault("jobs.acceptproxyaddress", false) {
|
||||
if proxiedAddress, isProxied := c.Request.Header["X-Forwarded-For"]; isProxied {
|
||||
remoteAddress = proxiedAddress[0]
|
||||
}
|
||||
}
|
||||
if !strings.HasPrefix(remoteAddress, "127.0.0.1") && !strings.HasPrefix(remoteAddress, "::1") {
|
||||
return c.Forbidden("%s is not local", remoteAddress)
|
||||
}
|
||||
entries := jobs.MainCron.Entries()
|
||||
return c.Render(entries)
|
||||
}
|
||||
|
||||
func init() {
|
||||
revel.TemplateFuncs["castjob"] = func(job cron.Job) *jobs.Job {
|
||||
return job.(*jobs.Job)
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
"github.com/robfig/cron"
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Job struct {
|
||||
Name string
|
||||
inner cron.Job
|
||||
status uint32
|
||||
running sync.Mutex
|
||||
}
|
||||
|
||||
const UNNAMED = "(unnamed)"
|
||||
|
||||
func New(job cron.Job) *Job {
|
||||
name := reflect.TypeOf(job).Name()
|
||||
if name == "Func" {
|
||||
name = UNNAMED
|
||||
}
|
||||
return &Job{
|
||||
Name: name,
|
||||
inner: job,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *Job) Status() string {
|
||||
if atomic.LoadUint32(&j.status) > 0 {
|
||||
return "RUNNING"
|
||||
}
|
||||
return "IDLE"
|
||||
}
|
||||
|
||||
func (j *Job) Run() {
|
||||
// If the job panics, just print a stack trace.
|
||||
// Don't let the whole process die.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if revelError := revel.NewErrorFromPanic(err); revelError != nil {
|
||||
revel.ERROR.Print(err, "\n", revelError.Stack)
|
||||
} else {
|
||||
revel.ERROR.Print(err, "\n", string(debug.Stack()))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if !selfConcurrent {
|
||||
j.running.Lock()
|
||||
defer j.running.Unlock()
|
||||
}
|
||||
|
||||
if workPermits != nil {
|
||||
workPermits <- struct{}{}
|
||||
defer func() { <-workPermits }()
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&j.status, 1)
|
||||
defer atomic.StoreUint32(&j.status, 0)
|
||||
|
||||
j.inner.Run()
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
// A job runner for executing scheduled or ad-hoc tasks asynchronously from HTTP requests.
|
||||
//
|
||||
// It adds a couple of features on top of the cron package to make it play nicely with Revel:
|
||||
// 1. Protection against job panics. (They print to ERROR instead of take down the process)
|
||||
// 2. (Optional) Limit on the number of jobs that may run simulatenously, to
|
||||
// limit resource consumption.
|
||||
// 3. (Optional) Protection against multiple instances of a single job running
|
||||
// concurrently. If one execution runs into the next, the next will be queued.
|
||||
// 4. Cron expressions may be defined in app.conf and are reusable across jobs.
|
||||
// 5. Job status reporting.
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
"github.com/robfig/cron"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Callers can use jobs.Func to wrap a raw func.
|
||||
// (Copying the type to this package makes it more visible)
|
||||
//
|
||||
// For example:
|
||||
// jobs.Schedule("cron.frequent", jobs.Func(myFunc))
|
||||
type Func func()
|
||||
|
||||
func (r Func) Run() { r() }
|
||||
|
||||
func Schedule(spec string, job cron.Job) error {
|
||||
// Look to see if given spec is a key from the Config.
|
||||
if strings.HasPrefix(spec, "cron.") {
|
||||
confSpec, found := revel.Config.String(spec)
|
||||
if !found {
|
||||
panic("Cron spec not found: " + spec)
|
||||
}
|
||||
spec = confSpec
|
||||
}
|
||||
sched, err := cron.Parse(spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
MainCron.Schedule(sched, New(job))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run the given job at a fixed interval.
|
||||
// The interval provided is the time between the job ending and the job being run again.
|
||||
// The time that the job takes to run is not included in the interval.
|
||||
func Every(duration time.Duration, job cron.Job) {
|
||||
MainCron.Schedule(cron.Every(duration), New(job))
|
||||
}
|
||||
|
||||
// Run the given job right now.
|
||||
func Now(job cron.Job) {
|
||||
go New(job).Run()
|
||||
}
|
||||
|
||||
// Run the given job once, after the given delay.
|
||||
func In(duration time.Duration, job cron.Job) {
|
||||
go func() {
|
||||
time.Sleep(duration)
|
||||
New(job).Run()
|
||||
}()
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package jobs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/revel/revel"
|
||||
"github.com/robfig/cron"
|
||||
)
|
||||
|
||||
const DEFAULT_JOB_POOL_SIZE = 10
|
||||
|
||||
var (
|
||||
// Singleton instance of the underlying job scheduler.
|
||||
MainCron *cron.Cron
|
||||
|
||||
// This limits the number of jobs allowed to run concurrently.
|
||||
workPermits chan struct{}
|
||||
|
||||
// Is a single job allowed to run concurrently with itself?
|
||||
selfConcurrent bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
MainCron = cron.New()
|
||||
revel.OnAppStart(func() {
|
||||
if size := revel.Config.IntDefault("jobs.pool", DEFAULT_JOB_POOL_SIZE); size > 0 {
|
||||
workPermits = make(chan struct{}, size)
|
||||
}
|
||||
selfConcurrent = revel.Config.BoolDefault("jobs.selfconcurrent", false)
|
||||
MainCron.Start()
|
||||
fmt.Println("Go to /@jobs to see job status.")
|
||||
})
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border: none;
|
||||
}
|
||||
table td, table th {
|
||||
padding: 4 10px;
|
||||
border: none;
|
||||
}
|
||||
table tr:nth-child(odd) {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Scheduled Jobs</h1>
|
||||
|
||||
<table>
|
||||
<tr><th>Name</th><th>Status</th><th>Last run</th><th>Next run</th></tr>
|
||||
{{range .entries}}
|
||||
{{$job := castjob .Job}}
|
||||
<tr>
|
||||
<td>{{$job.Name}}</td>
|
||||
<td>{{$job.Status}}</td>
|
||||
<td>{{if not .Prev.IsZero}}{{.Prev.Format "2006-01-02 15:04:05"}}{{end}}</td>
|
||||
<td>{{if not .Next.IsZero}}{{.Next.Format "2006-01-02 15:04:05"}}{{end}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
@@ -1 +0,0 @@
|
||||
GET /@jobs Jobs.Status
|
||||
@@ -1,19 +0,0 @@
|
||||
Revel pprof module
|
||||
============
|
||||
|
||||
#### How to use:
|
||||
|
||||
1. Open your app.conf file and add the following line:
|
||||
`module.pprof=github.com/revel/modules/pprof`
|
||||
This will enable the pprof module.
|
||||
|
||||
2. Next, open your routes file and add:
|
||||
`module:pprof` **Note:** Do not change these routes. The pprof command-line tool by default assumes these routes.
|
||||
|
||||
Congrats! You can now profile your application. To use the web interface, visit `http://<host>:<port>/debug/pprof`. You can also use the `go tool pprof` command to profile your application and get a little deeper. Use the command by running `go tool pprof <binary> http://<host>:<port>`. For example, if you modified the booking sample, you would run: `go tool pprof $GOPATH/bin/booking http://localhost:9000` (assuming you used the default `revel run` arguments.
|
||||
|
||||
The command-line tool will take a 30-second CPU profile, and save the results to a temporary file in your `$HOME/pprof` directory. You can reference this file at a later time by using the same command as above, but by specifying the filename instead of the server address.
|
||||
|
||||
In order to fully utilize the command-line tool, you may need the graphviz utilities. If you're on OS X, you can install these easily using Homebrew: `brew install graphviz`.
|
||||
|
||||
To read more about profiling Go programs, here is some reading material: [The Go Blog](http://blog.golang.org/profiling-go-programs) : [net/pprof package documentation](http://golang.org/pkg/net/http/pprof/)
|
||||
@@ -1,39 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
)
|
||||
|
||||
type Pprof struct {
|
||||
*revel.Controller
|
||||
}
|
||||
|
||||
// The PprofHandler type makes it easy to call the net/http/pprof handler methods
|
||||
// since they all have the same method signature
|
||||
type PprofHandler func(http.ResponseWriter, *http.Request)
|
||||
|
||||
func (r PprofHandler) Apply(req *revel.Request, resp *revel.Response) {
|
||||
r(resp.Out, req.Request)
|
||||
}
|
||||
|
||||
func (c Pprof) Profile() revel.Result {
|
||||
return PprofHandler(pprof.Profile)
|
||||
}
|
||||
|
||||
func (c Pprof) Symbol() revel.Result {
|
||||
return PprofHandler(pprof.Symbol)
|
||||
}
|
||||
|
||||
func (c Pprof) Cmdline() revel.Result {
|
||||
return PprofHandler(pprof.Cmdline)
|
||||
}
|
||||
|
||||
func (c Pprof) Trace() revel.Result {
|
||||
return PprofHandler(pprof.Trace)
|
||||
}
|
||||
|
||||
func (c Pprof) Index() revel.Result {
|
||||
return PprofHandler(pprof.Index)
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
GET /pprof/profile Pprof.Profile
|
||||
GET /pprof/symbol Pprof.Symbol
|
||||
GET /debug/pprof/cmdline Pprof.Cmdline
|
||||
GET /debug/pprof/trace Pprof.Trace
|
||||
GET /debug/pprof/* Pprof.Index
|
||||
GET /debug/pprof/ Pprof.Index
|
||||
@@ -1,121 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/revel/revel"
|
||||
"os"
|
||||
fpath "path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Static struct {
|
||||
*revel.Controller
|
||||
}
|
||||
|
||||
// This method handles requests for files. The supplied prefix may be absolute
|
||||
// or relative. If the prefix is relative it is assumed to be relative to the
|
||||
// application directory. The filepath may either be just a file or an
|
||||
// additional filepath to search for the given file. This response may return
|
||||
// the following responses in the event of an error or invalid request;
|
||||
// 403(Forbidden): If the prefix filepath combination results in a directory.
|
||||
// 404(Not found): If the prefix and filepath combination results in a non-existent file.
|
||||
// 500(Internal Server Error): There are a few edge cases that would likely indicate some configuration error outside of revel.
|
||||
//
|
||||
// Note that when defining routes in routes/conf the parameters must not have
|
||||
// spaces around the comma.
|
||||
// Bad: Static.Serve("public/img", "favicon.png")
|
||||
// Good: Static.Serve("public/img","favicon.png")
|
||||
//
|
||||
// Examples:
|
||||
// Serving a directory
|
||||
// Route (conf/routes):
|
||||
// GET /public/{<.*>filepath} Static.Serve("public")
|
||||
// Request:
|
||||
// public/js/sessvars.js
|
||||
// Calls
|
||||
// Static.Serve("public","js/sessvars.js")
|
||||
//
|
||||
// Serving a file
|
||||
// Route (conf/routes):
|
||||
// GET /favicon.ico Static.Serve("public/img","favicon.png")
|
||||
// Request:
|
||||
// favicon.ico
|
||||
// Calls:
|
||||
// Static.Serve("public/img", "favicon.png")
|
||||
func (c Static) Serve(prefix, filepath string) revel.Result {
|
||||
// Fix for #503.
|
||||
prefix = c.Params.Fixed.Get("prefix")
|
||||
if prefix == "" {
|
||||
return c.NotFound("")
|
||||
}
|
||||
|
||||
return serve(c, prefix, filepath)
|
||||
}
|
||||
|
||||
// This method allows modules to serve binary files. The parameters are the same
|
||||
// as Static.Serve with the additional module name pre-pended to the list of
|
||||
// arguments.
|
||||
func (c Static) ServeModule(moduleName, prefix, filepath string) revel.Result {
|
||||
// Fix for #503.
|
||||
prefix = c.Params.Fixed.Get("prefix")
|
||||
if prefix == "" {
|
||||
return c.NotFound("")
|
||||
}
|
||||
|
||||
var basePath string
|
||||
for _, module := range revel.Modules {
|
||||
if module.Name == moduleName {
|
||||
basePath = module.Path
|
||||
}
|
||||
}
|
||||
|
||||
absPath := fpath.Join(basePath, fpath.FromSlash(prefix))
|
||||
|
||||
return serve(c, absPath, filepath)
|
||||
}
|
||||
|
||||
|
||||
// This method allows static serving of application files in a verified manner.
|
||||
func serve(c Static, prefix, filepath string) revel.Result {
|
||||
var basePath string
|
||||
if !fpath.IsAbs(prefix) {
|
||||
basePath = revel.BasePath
|
||||
}
|
||||
|
||||
basePathPrefix := fpath.Join(basePath, fpath.FromSlash(prefix))
|
||||
fname := fpath.Join(basePathPrefix, fpath.FromSlash(filepath))
|
||||
// Verify the request file path is within the application's scope of access
|
||||
if !strings.HasPrefix(fname, basePathPrefix) {
|
||||
revel.WARN.Printf("Attempted to read file outside of base path: %s", fname)
|
||||
return c.NotFound("")
|
||||
}
|
||||
|
||||
// Verify file path is accessible
|
||||
finfo, err := os.Stat(fname)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) || err.(*os.PathError).Err == syscall.ENOTDIR {
|
||||
revel.WARN.Printf("File not found (%s): %s ", fname, err)
|
||||
return c.NotFound("File not found")
|
||||
}
|
||||
revel.ERROR.Printf("Error trying to get fileinfo for '%s': %s", fname, err)
|
||||
return c.RenderError(err)
|
||||
}
|
||||
|
||||
// Disallow directory listing
|
||||
if finfo.Mode().IsDir() {
|
||||
revel.WARN.Printf("Attempted directory listing of %s", fname)
|
||||
return c.Forbidden("Directory listing not allowed")
|
||||
}
|
||||
|
||||
// Open request file path
|
||||
file, err := os.Open(fname)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
revel.WARN.Printf("File not found (%s): %s ", fname, err)
|
||||
return c.NotFound("File not found")
|
||||
}
|
||||
revel.ERROR.Printf("Error opening '%s': %s", fname, err)
|
||||
return c.RenderError(err)
|
||||
}
|
||||
return c.RenderFile(file, revel.Inline)
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/revel/revel"
|
||||
"github.com/revel/revel/testing"
|
||||
)
|
||||
|
||||
// TestRunner is a controller which is used for running application tests in browser.
|
||||
type TestRunner struct {
|
||||
*revel.Controller
|
||||
}
|
||||
|
||||
// TestSuiteDesc is used for storing information about a single test suite.
|
||||
// This structure is required by revel test cmd.
|
||||
type TestSuiteDesc struct {
|
||||
Name string
|
||||
Tests []TestDesc
|
||||
|
||||
// Elem is reflect.Type which can be used for accessing methods
|
||||
// of the test suite.
|
||||
Elem reflect.Type
|
||||
}
|
||||
|
||||
// TestDesc is used for describing a single test of some test suite.
|
||||
// This structure is required by revel test cmd.
|
||||
type TestDesc struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
// TestSuiteResult stores the results the whole test suite.
|
||||
// This structure is required by revel test cmd.
|
||||
type TestSuiteResult struct {
|
||||
Name string
|
||||
Passed bool
|
||||
Results []TestResult
|
||||
}
|
||||
|
||||
// TestResult represents the results of running a single test of some test suite.
|
||||
// This structure is required by revel test cmd.
|
||||
type TestResult struct {
|
||||
Name string
|
||||
Passed bool
|
||||
ErrorHTML template.HTML
|
||||
ErrorSummary string
|
||||
}
|
||||
|
||||
var (
|
||||
testSuites []TestSuiteDesc // A list of all available tests.
|
||||
|
||||
none = []reflect.Value{} // It is used as input for reflect call in a few places.
|
||||
|
||||
// registeredTests simplifies the search of test suites by their name.
|
||||
// "TestSuite.TestName" is used as a key. Value represents index in testSuites.
|
||||
registeredTests map[string]int
|
||||
)
|
||||
|
||||
/*
|
||||
Controller's action methods are below.
|
||||
*/
|
||||
|
||||
// Index is an action which renders the full list of available test suites and their tests.
|
||||
func (c TestRunner) Index() revel.Result {
|
||||
return c.Render(testSuites)
|
||||
}
|
||||
|
||||
// Run runs a single test, given by the argument.
|
||||
func (c TestRunner) Run(suite, test string) revel.Result {
|
||||
// Check whether requested test exists.
|
||||
suiteIndex, ok := registeredTests[suite+"."+test]
|
||||
if !ok {
|
||||
return c.NotFound("Test %s.%s does not exist", suite, test)
|
||||
}
|
||||
|
||||
result := TestResult{Name: test}
|
||||
|
||||
// Found the suite, create a new instance and run the named method.
|
||||
t := testSuites[suiteIndex].Elem
|
||||
v := reflect.New(t)
|
||||
func() {
|
||||
// When the function stops executing try to recover from panic.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
// If panic error is empty, exit.
|
||||
panicErr := revel.NewErrorFromPanic(err)
|
||||
if panicErr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, prepare and format the response of server if possible.
|
||||
testSuite := v.Elem().FieldByName("TestSuite").Interface().(testing.TestSuite)
|
||||
res := formatResponse(testSuite)
|
||||
|
||||
// Render the error and save to the result structure.
|
||||
var buffer bytes.Buffer
|
||||
tmpl, _ := revel.MainTemplateLoader.Template("TestRunner/FailureDetail.html")
|
||||
tmpl.Render(&buffer, map[string]interface{}{
|
||||
"error": panicErr,
|
||||
"response": res,
|
||||
"postfix": suite + "_" + test,
|
||||
})
|
||||
result.ErrorSummary = errorSummary(panicErr)
|
||||
result.ErrorHTML = template.HTML(buffer.String())
|
||||
}
|
||||
}()
|
||||
|
||||
// Initialize the test suite with a NewTestSuite()
|
||||
testSuiteInstance := v.Elem().FieldByName("TestSuite")
|
||||
testSuiteInstance.Set(reflect.ValueOf(testing.NewTestSuite()))
|
||||
|
||||
// Make sure After method will be executed at the end.
|
||||
if m := v.MethodByName("After"); m.IsValid() {
|
||||
defer m.Call(none)
|
||||
}
|
||||
|
||||
// Start from running Before method of test suite if exists.
|
||||
if m := v.MethodByName("Before"); m.IsValid() {
|
||||
m.Call(none)
|
||||
}
|
||||
|
||||
// Start the test method itself.
|
||||
v.MethodByName(test).Call(none)
|
||||
|
||||
// No panic means success.
|
||||
result.Passed = true
|
||||
}()
|
||||
|
||||
return c.RenderJson(result)
|
||||
}
|
||||
|
||||
// List returns a JSON list of test suites and tests.
|
||||
// It is used by revel test command line tool.
|
||||
func (c TestRunner) List() revel.Result {
|
||||
return c.RenderJson(testSuites)
|
||||
}
|
||||
|
||||
/*
|
||||
Below are helper functions.
|
||||
*/
|
||||
|
||||
// describeSuite expects testsuite interface as input parameter
|
||||
// and returns its description in a form of TestSuiteDesc structure.
|
||||
func describeSuite(testSuite interface{}) TestSuiteDesc {
|
||||
t := reflect.TypeOf(testSuite)
|
||||
|
||||
// Get a list of methods of the embedded test type.
|
||||
// It will be used to make sure the same tests are not included in multiple test suites.
|
||||
super := t.Elem().Field(0).Type
|
||||
superMethods := map[string]bool{}
|
||||
for i := 0; i < super.NumMethod(); i++ {
|
||||
// Save the current method's name.
|
||||
superMethods[super.Method(i).Name] = true
|
||||
}
|
||||
|
||||
// Get a list of methods on the test suite that take no parameters, return
|
||||
// no results, and were not part of the embedded type's method set.
|
||||
var tests []TestDesc
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
m := t.Method(i)
|
||||
mt := m.Type
|
||||
|
||||
// Make sure the test method meets the criterias:
|
||||
// - method of testSuite without input parameters;
|
||||
// - nothing is returned;
|
||||
// - has "Test" prefix;
|
||||
// - doesn't belong to the embedded structure.
|
||||
methodWithoutParams := (mt.NumIn() == 1 && mt.In(0) == t)
|
||||
nothingReturned := (mt.NumOut() == 0)
|
||||
hasTestPrefix := (strings.HasPrefix(m.Name, "Test"))
|
||||
if methodWithoutParams && nothingReturned && hasTestPrefix && !superMethods[m.Name] {
|
||||
// Register the test suite's index so we can quickly find it by test's name later.
|
||||
registeredTests[t.Elem().Name()+"."+m.Name] = len(testSuites)
|
||||
|
||||
// Add test to the list of tests.
|
||||
tests = append(tests, TestDesc{m.Name})
|
||||
}
|
||||
}
|
||||
|
||||
return TestSuiteDesc{
|
||||
Name: t.Elem().Name(),
|
||||
Tests: tests,
|
||||
Elem: t.Elem(),
|
||||
}
|
||||
}
|
||||
|
||||
// errorSummary gets an error and returns its summary in human readable format.
|
||||
func errorSummary(err *revel.Error) (message string) {
|
||||
message = fmt.Sprintf("%4sStatus: %s\n%4sIn %s", "", err.Description, "", err.Path)
|
||||
|
||||
// If line of error isn't known return the message as is.
|
||||
if err.Line == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, include info about the line number and the relevant
|
||||
// source code lines.
|
||||
message += fmt.Sprintf(" (around line %d): ", err.Line)
|
||||
for _, line := range err.ContextSource() {
|
||||
if line.IsError {
|
||||
message += line.Source
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// formatResponse gets *revel.TestSuite as input parameter and
|
||||
// transform response related info into a readable format.
|
||||
func formatResponse(t testing.TestSuite) map[string]string {
|
||||
if t.Response == nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
// Beautify the response JSON to make it human readable.
|
||||
resp, err := json.MarshalIndent(t.Response, "", " ")
|
||||
if err != nil {
|
||||
revel.ERROR.Println(err)
|
||||
}
|
||||
|
||||
// Remove extra new line symbols so they do not take too much space on a result page.
|
||||
// Allow no more than 1 line break at a time.
|
||||
body := strings.Replace(string(t.ResponseBody), "\n\n", "\n", -1)
|
||||
body = strings.Replace(body, "\r\n\r\n", "\r\n", -1)
|
||||
|
||||
return map[string]string{
|
||||
"Headers": string(resp),
|
||||
"Body": strings.TrimSpace(body),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Every time app is restarted convert the list of available test suites
|
||||
// provided by the revel testing package into a format which will be used by
|
||||
// the testrunner module and revel test cmd.
|
||||
revel.OnAppStart(func() {
|
||||
// Extracting info about available test suites from revel/testing package.
|
||||
registeredTests = map[string]int{}
|
||||
for _, testSuite := range testing.TestSuites {
|
||||
testSuites = append(testSuites, describeSuite(testSuite))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
func init() {
|
||||
revel.OnAppStart(func() {
|
||||
fmt.Println("Go to /@tests to run the tests.")
|
||||
})
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<b>{{.error.Description}}</b>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="active"><a href="#error_{{.postfix}}" role="tab" data-toggle="tab">Error</a></li>
|
||||
<li><a href="#stack_{{.postfix}}" role="tab" data-toggle="tab">Stack</a></li>
|
||||
{{if .response}}
|
||||
<li><a href="#headers_{{.postfix}}" role="tab" data-toggle="tab">Headers</a></li>
|
||||
<li><a href="#body_{{.postfix}}" role="tab" data-toggle="tab">Response Body</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="result_{{.postfix}}">
|
||||
<div class="tab-pane active" id="error_{{.postfix}}">
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading">
|
||||
In {{.error.Path}}{{if .error.Line}} (around {{if .error.Line}}line {{.error.Line}}{{end}}{{if .error.Column}} column {{.error.Column}}{{end}}){{end}}:
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{{range .error.ContextSource}}
|
||||
{{if .IsError}}
|
||||
<pre><code class="go">{{.Source}}</code></pre>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="stack_{{.postfix}}">
|
||||
<pre><code class="bash">{{.error.Stack}}</code></pre>
|
||||
</div>
|
||||
{{if .response}}
|
||||
<div class="tab-pane" id="headers_{{.postfix}}">
|
||||
<pre><code class="json">{{.response.Headers}}</code></pre>
|
||||
</div>
|
||||
<div class="tab-pane" id="body_{{.postfix}}">
|
||||
<pre><code class="html">{{.response.Body}}</code></pre>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,110 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Revel Test Runner</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<link href="{{url `Root`}}/@tests/public/css/bootstrap.min.css" type="text/css" rel="stylesheet"></link>
|
||||
<link href="{{url `Root`}}/@tests/public/css/github.css" type="text/css" rel="stylesheet"></link>
|
||||
<script src="{{url `Root`}}/@tests/public/js/jquery-1.9.1.min.js" type="text/javascript"></script>
|
||||
<script src="{{url `Root`}}/@tests/public/js/bootstrap.min.js" type="text/javascript"></script>
|
||||
<script src="{{url `Root`}}/@tests/public/js/highlight.pack.js" type="text/javascript"></script>
|
||||
<style>
|
||||
header { padding:20px 0; background-color:#ADD8E6 }
|
||||
.passed td { background-color: #90EE90 !important; }
|
||||
.failed td { background-color: #FFB6C1 !important; }
|
||||
.tests td.name, .tests td.result { padding-top: 13px; }
|
||||
pre { font-size:10px; white-space: pre; }
|
||||
.name { width: 25%; }
|
||||
.w100 { width: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<table class="w100"><tr><td>
|
||||
<h1>Test Runner</h1>
|
||||
<p class="lead">Run all of your application's tests from here.</p>
|
||||
</td><td style="padding-left:150px;" class="text-right">
|
||||
<button class="btn btn-lg btn-success" all-tests="">Run All Tests</button>
|
||||
</td></tr></table>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
{{range .testSuites}}
|
||||
<p class="lead" style="margin-top:20px;">{{.Name}}</p>
|
||||
<table class="table table-striped tests" suite="{{.Name}}">
|
||||
{{range .Tests}}
|
||||
<tr>
|
||||
<td class="name">{{.Name}}</td>
|
||||
<td class="result">
|
||||
</td>
|
||||
<td class="text-right"><button test="{{.Name}}" class="btn btn-success">Run</button></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var buttons = [];
|
||||
var running;
|
||||
|
||||
$("button[test]").click(function() {
|
||||
var button = $(this).addClass("disabled").text("Running");
|
||||
addToQueue(button);
|
||||
});
|
||||
|
||||
$("button[all-tests]").click(function() {
|
||||
var button = $(this).addClass("disabled").text("Running");
|
||||
$("button[test]").click();
|
||||
});
|
||||
|
||||
function addToQueue(button) {
|
||||
buttons.push(button);
|
||||
if (!running) {
|
||||
running = true;
|
||||
nextTest();
|
||||
}
|
||||
}
|
||||
|
||||
function nextTest() {
|
||||
if (buttons.length == 0) {
|
||||
running = false;
|
||||
} else {
|
||||
var next = buttons.shift();
|
||||
runTest(next);
|
||||
}
|
||||
}
|
||||
|
||||
function runTest(button) {
|
||||
var suite = button.parents("table").attr("suite");
|
||||
var test = button.attr("test");
|
||||
var row = button.parents("tr");
|
||||
var resultCell = row.children(".result");
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url: "{{url `Root`}}/@tests/"+suite+"/"+test,
|
||||
success: function(result) {
|
||||
row.attr("class", result.Passed ? "passed" : "failed");
|
||||
if (result.Passed) {
|
||||
resultCell.html("");
|
||||
} else {
|
||||
resultCell.html(result.ErrorHTML);
|
||||
|
||||
$("#result_" + suite + "_" + test + " pre code").each(function(i, block) {
|
||||
hljs.highlightBlock(block);
|
||||
});
|
||||
}
|
||||
button.removeClass("disabled").text("Run");
|
||||
if (buttons.length == 0) {
|
||||
$("button[all-tests]").removeClass("disabled").text("Run All Tests");
|
||||
}
|
||||
nextTest();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,48 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Revel Test Runner</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #333333;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
header { padding:20px 0; }
|
||||
header.passed { background-color: #90EE90 !important; }
|
||||
header.failed { background-color: #FFB6C1 !important; }
|
||||
table { margin-top: 20px; padding: 8px; line-height: 20px; }
|
||||
td { vertical-align: top; padding-right:20px; }
|
||||
a { color: #0088cc; }
|
||||
.container { margin-left: auto; margin-right: auto; width: 940px; overflow: hidden; }
|
||||
.result h2 { font-size: 16px; border-bottom: 1px solid #f0f0f0; padding-bottom: 0.2em; }
|
||||
.result.failed b { font-weight:bold; color: #C00; font-size: 14px; }
|
||||
.result.failed h2 { color: #C00; }
|
||||
.result .info { font-size: 12px; }
|
||||
.result .info pre { overflow: auto; background-color: #f0f0f0; width: 100%; max-height: 500px; }
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<header class="{{if .Passed}}passed{{else}}failed{{end}}">
|
||||
<div class="container">
|
||||
<h1>{{.Name}}</h1>
|
||||
<p>{{if .Passed}}PASSED{{else}}FAILED{{end}}</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
{{range .Results}}
|
||||
<div class="result {{if .Passed}}passed{{else}}failed{{end}}">
|
||||
<div><h2>{{.Name}}</h2></div>
|
||||
<div class="info">{{if .ErrorHTML}}{{.ErrorHTML}}{{else}}PASSED{{end}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +0,0 @@
|
||||
GET /@tests TestRunner.Index
|
||||
GET /@tests.list TestRunner.List
|
||||
GET /@tests/public/*filepath Static.ServeModule(testrunner,public)
|
||||
GET /@tests/:suite/:test TestRunner.Run
|
||||
File diff suppressed because one or more lines are too long
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
|
||||
github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
color: #333;
|
||||
background: #f8f8f8;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-template_comment,
|
||||
.diff .hljs-header,
|
||||
.hljs-javadoc {
|
||||
color: #998;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.css .rule .hljs-keyword,
|
||||
.hljs-winutils,
|
||||
.javascript .hljs-title,
|
||||
.nginx .hljs-title,
|
||||
.hljs-subst,
|
||||
.hljs-request,
|
||||
.hljs-status {
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-hexcolor,
|
||||
.ruby .hljs-constant {
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-tag .hljs-value,
|
||||
.hljs-phpdoc,
|
||||
.hljs-dartdoc,
|
||||
.tex .hljs-formula {
|
||||
color: #d14;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-id,
|
||||
.scss .hljs-preprocessor {
|
||||
color: #900;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.javascript .hljs-title,
|
||||
.hljs-list .hljs-keyword,
|
||||
.hljs-subst {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.hljs-class .hljs-title,
|
||||
.hljs-type,
|
||||
.vhdl .hljs-literal,
|
||||
.tex .hljs-command {
|
||||
color: #458;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-tag,
|
||||
.hljs-tag .hljs-title,
|
||||
.hljs-rules .hljs-property,
|
||||
.django .hljs-tag .hljs-keyword {
|
||||
color: #000080;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.hljs-attribute,
|
||||
.hljs-variable,
|
||||
.lisp .hljs-body {
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
.hljs-regexp {
|
||||
color: #009926;
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.ruby .hljs-symbol .hljs-string,
|
||||
.lisp .hljs-keyword,
|
||||
.clojure .hljs-keyword,
|
||||
.scheme .hljs-keyword,
|
||||
.tex .hljs-special,
|
||||
.hljs-prompt {
|
||||
color: #990073;
|
||||
}
|
||||
|
||||
.hljs-built_in {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-preprocessor,
|
||||
.hljs-pragma,
|
||||
.hljs-pi,
|
||||
.hljs-doctype,
|
||||
.hljs-shebang,
|
||||
.hljs-cdata {
|
||||
color: #999;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
background: #fdd;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
background: #dfd;
|
||||
}
|
||||
|
||||
.diff .hljs-change {
|
||||
background: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-chunk {
|
||||
color: #aaa;
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.5 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,545 +0,0 @@
|
||||
3dm=x-world/x-3dmf
|
||||
3dmf=x-world/x-3dmf
|
||||
7z=application/x-7z-compressed
|
||||
a=application/octet-stream
|
||||
aab=application/x-authorware-bin
|
||||
aam=application/x-authorware-map
|
||||
aas=application/x-authorware-seg
|
||||
abc=text/vndabc
|
||||
ace=application/x-ace-compressed
|
||||
acgi=text/html
|
||||
afl=video/animaflex
|
||||
ai=application/postscript
|
||||
aif=audio/aiff
|
||||
aifc=audio/aiff
|
||||
aiff=audio/aiff
|
||||
aim=application/x-aim
|
||||
aip=text/x-audiosoft-intra
|
||||
alz=application/x-alz-compressed
|
||||
ani=application/x-navi-animation
|
||||
aos=application/x-nokia-9000-communicator-add-on-software
|
||||
aps=application/mime
|
||||
arc=application/x-arc-compressed
|
||||
arj=application/arj
|
||||
art=image/x-jg
|
||||
asf=video/x-ms-asf
|
||||
asm=text/x-asm
|
||||
asp=text/asp
|
||||
asx=application/x-mplayer2
|
||||
au=audio/basic
|
||||
avi=video/x-msvideo
|
||||
avs=video/avs-video
|
||||
bcpio=application/x-bcpio
|
||||
bin=application/mac-binary
|
||||
bmp=image/bmp
|
||||
boo=application/book
|
||||
book=application/book
|
||||
boz=application/x-bzip2
|
||||
bsh=application/x-bsh
|
||||
bz2=application/x-bzip2
|
||||
bz=application/x-bzip
|
||||
c++=text/plain
|
||||
c=text/x-c
|
||||
cab=application/vnd.ms-cab-compressed
|
||||
cat=application/vndms-pkiseccat
|
||||
cc=text/x-c
|
||||
ccad=application/clariscad
|
||||
cco=application/x-cocoa
|
||||
cdf=application/cdf
|
||||
cer=application/pkix-cert
|
||||
cha=application/x-chat
|
||||
chat=application/x-chat
|
||||
chrt=application/vnd.kde.kchart
|
||||
class=application/java
|
||||
# ? class=application/java-vm
|
||||
com=text/plain
|
||||
conf=text/plain
|
||||
cpio=application/x-cpio
|
||||
cpp=text/x-c
|
||||
cpt=application/mac-compactpro
|
||||
crl=application/pkcs-crl
|
||||
crt=application/pkix-cert
|
||||
crx=application/x-chrome-extension
|
||||
csh=text/x-scriptcsh
|
||||
css=text/css
|
||||
csv=text/csv
|
||||
cxx=text/plain
|
||||
dar=application/x-dar
|
||||
dcr=application/x-director
|
||||
deb=application/x-debian-package
|
||||
deepv=application/x-deepv
|
||||
def=text/plain
|
||||
der=application/x-x509-ca-cert
|
||||
dif=video/x-dv
|
||||
dir=application/x-director
|
||||
divx=video/divx
|
||||
dl=video/dl
|
||||
dmg=application/x-apple-diskimage
|
||||
doc=application/msword
|
||||
dot=application/msword
|
||||
dp=application/commonground
|
||||
drw=application/drafting
|
||||
dump=application/octet-stream
|
||||
dv=video/x-dv
|
||||
dvi=application/x-dvi
|
||||
dwf=drawing/x-dwf=(old)
|
||||
dwg=application/acad
|
||||
dxf=application/dxf
|
||||
dxr=application/x-director
|
||||
el=text/x-scriptelisp
|
||||
elc=application/x-bytecodeelisp=(compiled=elisp)
|
||||
eml=message/rfc822
|
||||
env=application/x-envoy
|
||||
eps=application/postscript
|
||||
es=application/x-esrehber
|
||||
etx=text/x-setext
|
||||
evy=application/envoy
|
||||
exe=application/octet-stream
|
||||
f77=text/x-fortran
|
||||
f90=text/x-fortran
|
||||
f=text/x-fortran
|
||||
fdf=application/vndfdf
|
||||
fif=application/fractals
|
||||
fli=video/fli
|
||||
flo=image/florian
|
||||
flv=video/x-flv
|
||||
flx=text/vndfmiflexstor
|
||||
fmf=video/x-atomic3d-feature
|
||||
for=text/x-fortran
|
||||
fpx=image/vndfpx
|
||||
frl=application/freeloader
|
||||
funk=audio/make
|
||||
g3=image/g3fax
|
||||
g=text/plain
|
||||
gif=image/gif
|
||||
gl=video/gl
|
||||
gsd=audio/x-gsm
|
||||
gsm=audio/x-gsm
|
||||
gsp=application/x-gsp
|
||||
gss=application/x-gss
|
||||
gtar=application/x-gtar
|
||||
gz=application/x-compressed
|
||||
gzip=application/x-gzip
|
||||
h=text/x-h
|
||||
hdf=application/x-hdf
|
||||
help=application/x-helpfile
|
||||
hgl=application/vndhp-hpgl
|
||||
hh=text/x-h
|
||||
hlb=text/x-script
|
||||
hlp=application/hlp
|
||||
hpg=application/vndhp-hpgl
|
||||
hpgl=application/vndhp-hpgl
|
||||
hqx=application/binhex
|
||||
hta=application/hta
|
||||
htc=text/x-component
|
||||
htm=text/html
|
||||
html=text/html
|
||||
htmls=text/html
|
||||
htt=text/webviewhtml
|
||||
htx=text/html
|
||||
ice=x-conference/x-cooltalk
|
||||
ico=image/x-icon
|
||||
ics=text/calendar
|
||||
icz=text/calendar
|
||||
idc=text/plain
|
||||
ief=image/ief
|
||||
iefs=image/ief
|
||||
iges=application/iges
|
||||
igs=application/iges
|
||||
ima=application/x-ima
|
||||
imap=application/x-httpd-imap
|
||||
inf=application/inf
|
||||
ins=application/x-internett-signup
|
||||
ip=application/x-ip2
|
||||
isu=video/x-isvideo
|
||||
it=audio/it
|
||||
iv=application/x-inventor
|
||||
ivr=i-world/i-vrml
|
||||
ivy=application/x-livescreen
|
||||
jam=audio/x-jam
|
||||
jav=text/x-java-source
|
||||
java=text/x-java-source
|
||||
jcm=application/x-java-commerce
|
||||
jfif-tbnl=image/jpeg
|
||||
jfif=image/jpeg
|
||||
jnlp=application/x-java-jnlp-file
|
||||
jpe=image/jpeg
|
||||
jpeg=image/jpeg
|
||||
jpg=image/jpeg
|
||||
jps=image/x-jps
|
||||
js=application/javascript
|
||||
json=application/json
|
||||
jut=image/jutvision
|
||||
kar=audio/midi
|
||||
karbon=application/vnd.kde.karbon
|
||||
kfo=application/vnd.kde.kformula
|
||||
flw=application/vnd.kde.kivio
|
||||
kml=application/vnd.google-earth.kml+xml
|
||||
kmz=application/vnd.google-earth.kmz
|
||||
kon=application/vnd.kde.kontour
|
||||
kpr=application/vnd.kde.kpresenter
|
||||
kpt=application/vnd.kde.kpresenter
|
||||
ksp=application/vnd.kde.kspread
|
||||
kwd=application/vnd.kde.kword
|
||||
kwt=application/vnd.kde.kword
|
||||
ksh=text/x-scriptksh
|
||||
la=audio/nspaudio
|
||||
lam=audio/x-liveaudio
|
||||
latex=application/x-latex
|
||||
lha=application/lha
|
||||
lhx=application/octet-stream
|
||||
list=text/plain
|
||||
lma=audio/nspaudio
|
||||
log=text/plain
|
||||
lsp=text/x-scriptlisp
|
||||
lst=text/plain
|
||||
lsx=text/x-la-asf
|
||||
ltx=application/x-latex
|
||||
lzh=application/octet-stream
|
||||
lzx=application/lzx
|
||||
m1v=video/mpeg
|
||||
m2a=audio/mpeg
|
||||
m2v=video/mpeg
|
||||
m3u=audio/x-mpegurl
|
||||
m=text/x-m
|
||||
man=application/x-troff-man
|
||||
manifest=text/cache-manifest
|
||||
map=application/x-navimap
|
||||
mar=text/plain
|
||||
mbd=application/mbedlet
|
||||
mc$=application/x-magic-cap-package-10
|
||||
mcd=application/mcad
|
||||
mcf=text/mcf
|
||||
mcp=application/netmc
|
||||
me=application/x-troff-me
|
||||
mht=message/rfc822
|
||||
mhtml=message/rfc822
|
||||
mid=application/x-midi
|
||||
midi=application/x-midi
|
||||
mif=application/x-frame
|
||||
mime=message/rfc822
|
||||
mjf=audio/x-vndaudioexplosionmjuicemediafile
|
||||
mjpg=video/x-motion-jpeg
|
||||
mm=application/base64
|
||||
mme=application/base64
|
||||
mod=audio/mod
|
||||
moov=video/quicktime
|
||||
mov=video/quicktime
|
||||
movie=video/x-sgi-movie
|
||||
mp2=audio/mpeg
|
||||
mp3=audio/mpeg3
|
||||
mp4=video/mp4
|
||||
mpa=audio/mpeg
|
||||
mpc=application/x-project
|
||||
mpe=video/mpeg
|
||||
mpeg=video/mpeg
|
||||
mpg=video/mpeg
|
||||
mpga=audio/mpeg
|
||||
mpp=application/vndms-project
|
||||
mpt=application/x-project
|
||||
mpv=application/x-project
|
||||
mpx=application/x-project
|
||||
mrc=application/marc
|
||||
ms=application/x-troff-ms
|
||||
mv=video/x-sgi-movie
|
||||
my=audio/make
|
||||
mzz=application/x-vndaudioexplosionmzz
|
||||
nap=image/naplps
|
||||
naplps=image/naplps
|
||||
nc=application/x-netcdf
|
||||
ncm=application/vndnokiaconfiguration-message
|
||||
nif=image/x-niff
|
||||
niff=image/x-niff
|
||||
nix=application/x-mix-transfer
|
||||
nsc=application/x-conference
|
||||
nvd=application/x-navidoc
|
||||
o=application/octet-stream
|
||||
oda=application/oda
|
||||
odb=application/vnd.oasis.opendocument.database
|
||||
odc=application/vnd.oasis.opendocument.chart
|
||||
odf=application/vnd.oasis.opendocument.formula
|
||||
odg=application/vnd.oasis.opendocument.graphics
|
||||
odi=application/vnd.oasis.opendocument.image
|
||||
odm=application/vnd.oasis.opendocument.text-master
|
||||
odp=application/vnd.oasis.opendocument.presentation
|
||||
ods=application/vnd.oasis.opendocument.spreadsheet
|
||||
odt=application/vnd.oasis.opendocument.text
|
||||
oga=audio/ogg
|
||||
ogg=audio/ogg
|
||||
ogv=video/ogg
|
||||
omc=application/x-omc
|
||||
omcd=application/x-omcdatamaker
|
||||
omcr=application/x-omcregerator
|
||||
otc=application/vnd.oasis.opendocument.chart-template
|
||||
otf=application/vnd.oasis.opendocument.formula-template
|
||||
otg=application/vnd.oasis.opendocument.graphics-template
|
||||
oth=application/vnd.oasis.opendocument.text-web
|
||||
oti=application/vnd.oasis.opendocument.image-template
|
||||
otm=application/vnd.oasis.opendocument.text-master
|
||||
otp=application/vnd.oasis.opendocument.presentation-template
|
||||
ots=application/vnd.oasis.opendocument.spreadsheet-template
|
||||
ott=application/vnd.oasis.opendocument.text-template
|
||||
p10=application/pkcs10
|
||||
p12=application/pkcs-12
|
||||
p7a=application/x-pkcs7-signature
|
||||
p7c=application/pkcs7-mime
|
||||
p7m=application/pkcs7-mime
|
||||
p7r=application/x-pkcs7-certreqresp
|
||||
p7s=application/pkcs7-signature
|
||||
p=text/x-pascal
|
||||
part=application/pro_eng
|
||||
pas=text/pascal
|
||||
pbm=image/x-portable-bitmap
|
||||
pcl=application/vndhp-pcl
|
||||
pct=image/x-pict
|
||||
pcx=image/x-pcx
|
||||
pdb=chemical/x-pdb
|
||||
pdf=application/pdf
|
||||
pfunk=audio/make
|
||||
pgm=image/x-portable-graymap
|
||||
pic=image/pict
|
||||
pict=image/pict
|
||||
pkg=application/x-newton-compatible-pkg
|
||||
pko=application/vndms-pkipko
|
||||
pl=text/x-scriptperl
|
||||
plx=application/x-pixclscript
|
||||
pm4=application/x-pagemaker
|
||||
pm5=application/x-pagemaker
|
||||
pm=text/x-scriptperl-module
|
||||
png=image/png
|
||||
pnm=application/x-portable-anymap
|
||||
pot=application/mspowerpoint
|
||||
pov=model/x-pov
|
||||
ppa=application/vndms-powerpoint
|
||||
ppm=image/x-portable-pixmap
|
||||
pps=application/mspowerpoint
|
||||
ppt=application/mspowerpoint
|
||||
ppz=application/mspowerpoint
|
||||
pre=application/x-freelance
|
||||
prt=application/pro_eng
|
||||
ps=application/postscript
|
||||
psd=application/octet-stream
|
||||
pvu=paleovu/x-pv
|
||||
pwz=application/vndms-powerpoint
|
||||
py=text/x-scriptphyton
|
||||
pyc=applicaiton/x-bytecodepython
|
||||
qcp=audio/vndqcelp
|
||||
qd3=x-world/x-3dmf
|
||||
qd3d=x-world/x-3dmf
|
||||
qif=image/x-quicktime
|
||||
qt=video/quicktime
|
||||
qtc=video/x-qtc
|
||||
qti=image/x-quicktime
|
||||
qtif=image/x-quicktime
|
||||
ra=audio/x-pn-realaudio
|
||||
ram=audio/x-pn-realaudio
|
||||
rar=application/x-rar-compressed
|
||||
ras=application/x-cmu-raster
|
||||
rast=image/cmu-raster
|
||||
rexx=text/x-scriptrexx
|
||||
rf=image/vndrn-realflash
|
||||
rgb=image/x-rgb
|
||||
rm=application/vndrn-realmedia
|
||||
rmi=audio/mid
|
||||
rmm=audio/x-pn-realaudio
|
||||
rmp=audio/x-pn-realaudio
|
||||
rng=application/ringing-tones
|
||||
rnx=application/vndrn-realplayer
|
||||
roff=application/x-troff
|
||||
rp=image/vndrn-realpix
|
||||
rpm=audio/x-pn-realaudio-plugin
|
||||
rt=text/vndrn-realtext
|
||||
rtf=text/richtext
|
||||
rtx=text/richtext
|
||||
rv=video/vndrn-realvideo
|
||||
s=text/x-asm
|
||||
s3m=audio/s3m
|
||||
s7z=application/x-7z-compressed
|
||||
saveme=application/octet-stream
|
||||
sbk=application/x-tbook
|
||||
scm=text/x-scriptscheme
|
||||
sdml=text/plain
|
||||
sdp=application/sdp
|
||||
sdr=application/sounder
|
||||
sea=application/sea
|
||||
set=application/set
|
||||
sgm=text/x-sgml
|
||||
sgml=text/x-sgml
|
||||
sh=text/x-scriptsh
|
||||
shar=application/x-bsh
|
||||
shtml=text/x-server-parsed-html
|
||||
sid=audio/x-psid
|
||||
skd=application/x-koan
|
||||
skm=application/x-koan
|
||||
skp=application/x-koan
|
||||
skt=application/x-koan
|
||||
sit=application/x-stuffit
|
||||
sitx=application/x-stuffitx
|
||||
sl=application/x-seelogo
|
||||
smi=application/smil
|
||||
smil=application/smil
|
||||
snd=audio/basic
|
||||
sol=application/solids
|
||||
spc=text/x-speech
|
||||
spl=application/futuresplash
|
||||
spr=application/x-sprite
|
||||
sprite=application/x-sprite
|
||||
spx=audio/ogg
|
||||
src=application/x-wais-source
|
||||
ssi=text/x-server-parsed-html
|
||||
ssm=application/streamingmedia
|
||||
sst=application/vndms-pkicertstore
|
||||
step=application/step
|
||||
stl=application/sla
|
||||
stp=application/step
|
||||
sv4cpio=application/x-sv4cpio
|
||||
sv4crc=application/x-sv4crc
|
||||
svf=image/vnddwg
|
||||
svg=image/svg+xml
|
||||
svr=application/x-world
|
||||
swf=application/x-shockwave-flash
|
||||
t=application/x-troff
|
||||
talk=text/x-speech
|
||||
tar=application/x-tar
|
||||
tbk=application/toolbook
|
||||
tcl=text/x-scripttcl
|
||||
tcsh=text/x-scripttcsh
|
||||
tex=application/x-tex
|
||||
texi=application/x-texinfo
|
||||
texinfo=application/x-texinfo
|
||||
text=text/plain
|
||||
tgz=application/gnutar
|
||||
tif=image/tiff
|
||||
tiff=image/tiff
|
||||
tr=application/x-troff
|
||||
tsi=audio/tsp-audio
|
||||
tsp=application/dsptype
|
||||
tsv=text/tab-separated-values
|
||||
turbot=image/florian
|
||||
txt=text/plain
|
||||
uil=text/x-uil
|
||||
uni=text/uri-list
|
||||
unis=text/uri-list
|
||||
unv=application/i-deas
|
||||
uri=text/uri-list
|
||||
uris=text/uri-list
|
||||
ustar=application/x-ustar
|
||||
uu=text/x-uuencode
|
||||
uue=text/x-uuencode
|
||||
vcd=application/x-cdlink
|
||||
vcf=text/x-vcard
|
||||
vcard=text/x-vcard
|
||||
vcs=text/x-vcalendar
|
||||
vda=application/vda
|
||||
vdo=video/vdo
|
||||
vew=application/groupwise
|
||||
viv=video/vivo
|
||||
vivo=video/vivo
|
||||
vmd=application/vocaltec-media-desc
|
||||
vmf=application/vocaltec-media-file
|
||||
voc=audio/voc
|
||||
vos=video/vosaic
|
||||
vox=audio/voxware
|
||||
vqe=audio/x-twinvq-plugin
|
||||
vqf=audio/x-twinvq
|
||||
vql=audio/x-twinvq-plugin
|
||||
vrml=application/x-vrml
|
||||
vrt=x-world/x-vrt
|
||||
vsd=application/x-visio
|
||||
vst=application/x-visio
|
||||
vsw=application/x-visio
|
||||
w60=application/wordperfect60
|
||||
w61=application/wordperfect61
|
||||
w6w=application/msword
|
||||
wav=audio/wav
|
||||
wb1=application/x-qpro
|
||||
wbmp=image/vnd.wap.wbmp
|
||||
web=application/vndxara
|
||||
wiz=application/msword
|
||||
wk1=application/x-123
|
||||
wmf=windows/metafile
|
||||
wml=text/vnd.wap.wml
|
||||
wmlc=application/vnd.wap.wmlc
|
||||
wmls=text/vnd.wap.wmlscript
|
||||
wmlsc=application/vnd.wap.wmlscriptc
|
||||
word=application/msword
|
||||
wp5=application/wordperfect
|
||||
wp6=application/wordperfect
|
||||
wp=application/wordperfect
|
||||
wpd=application/wordperfect
|
||||
wq1=application/x-lotus
|
||||
wri=application/mswrite
|
||||
wrl=application/x-world
|
||||
wrz=model/vrml
|
||||
wsc=text/scriplet
|
||||
wsrc=application/x-wais-source
|
||||
wtk=application/x-wintalk
|
||||
x-png=image/png
|
||||
xbm=image/x-xbitmap
|
||||
xdr=video/x-amt-demorun
|
||||
xgz=xgl/drawing
|
||||
xif=image/vndxiff
|
||||
xl=application/excel
|
||||
xla=application/excel
|
||||
xlb=application/excel
|
||||
xlc=application/excel
|
||||
xld=application/excel
|
||||
xlk=application/excel
|
||||
xll=application/excel
|
||||
xlm=application/excel
|
||||
xls=application/excel
|
||||
xlt=application/excel
|
||||
xlv=application/excel
|
||||
xlw=application/excel
|
||||
xm=audio/xm
|
||||
xml=text/xml
|
||||
xmz=xgl/movie
|
||||
xpix=application/x-vndls-xpix
|
||||
xpm=image/x-xpixmap
|
||||
xsr=video/x-amt-showrun
|
||||
xwd=image/x-xwd
|
||||
xyz=chemical/x-pdb
|
||||
z=application/x-compress
|
||||
zip=application/zip
|
||||
zoo=application/octet-stream
|
||||
zsh=text/x-scriptzsh
|
||||
# Office 2007 mess - http://wdg.uncc.edu/Microsoft_Office_2007_MIME_Types_for_Apache_and_IIS
|
||||
docx=application/vnd.openxmlformats-officedocument.wordprocessingml.document
|
||||
docm=application/vnd.ms-word.document.macroEnabled.12
|
||||
dotx=application/vnd.openxmlformats-officedocument.wordprocessingml.template
|
||||
dotm=application/vnd.ms-word.template.macroEnabled.12
|
||||
xlsx=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||
xlsm=application/vnd.ms-excel.sheet.macroEnabled.12
|
||||
xltx=application/vnd.openxmlformats-officedocument.spreadsheetml.template
|
||||
xltm=application/vnd.ms-excel.template.macroEnabled.12
|
||||
xlsb=application/vnd.ms-excel.sheet.binary.macroEnabled.12
|
||||
xlam=application/vnd.ms-excel.addin.macroEnabled.12
|
||||
pptx=application/vnd.openxmlformats-officedocument.presentationml.presentation
|
||||
pptm=application/vnd.ms-powerpoint.presentation.macroEnabled.12
|
||||
ppsx=application/vnd.openxmlformats-officedocument.presentationml.slideshow
|
||||
ppsm=application/vnd.ms-powerpoint.slideshow.macroEnabled.12
|
||||
potx=application/vnd.openxmlformats-officedocument.presentationml.template
|
||||
potm=application/vnd.ms-powerpoint.template.macroEnabled.12
|
||||
ppam=application/vnd.ms-powerpoint.addin.macroEnabled.12
|
||||
sldx=application/vnd.openxmlformats-officedocument.presentationml.slide
|
||||
sldm=application/vnd.ms-powerpoint.slide.macroEnabled.12
|
||||
thmx=application/vnd.ms-officetheme
|
||||
onetoc=application/onenote
|
||||
onetoc2=application/onenote
|
||||
onetmp=application/onenote
|
||||
onepkg=application/onenote
|
||||
# koffice
|
||||
|
||||
# iWork
|
||||
key=application/x-iwork-keynote-sffkey
|
||||
kth=application/x-iwork-keynote-sffkth
|
||||
nmbtemplate=application/x-iwork-numbers-sfftemplate
|
||||
numbers=application/x-iwork-numbers-sffnumbers
|
||||
pages=application/x-iwork-pages-sffpages
|
||||
template=application/x-iwork-pages-sfftemplate
|
||||
|
||||
# Extensions for Mozilla apps (Firefox and friends)
|
||||
xpi=application/x-xpinstall
|
||||
|
||||
# Opera extensions
|
||||
oex=application/x-opera-extension
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Forbidden</title>
|
||||
</head>
|
||||
<body>
|
||||
{{with .Error}}
|
||||
<h1>
|
||||
{{.Title}}
|
||||
</h1>
|
||||
<p>
|
||||
{{.Description}}
|
||||
</p>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"title": "{{js .Error.Title}}",
|
||||
"description": "{{js .Error.Description}}"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{{.Error.Title}}
|
||||
|
||||
{{.Error.Description}}
|
||||
@@ -1 +0,0 @@
|
||||
<forbidden>{{.Error.Description}}</forbidden>
|
||||
@@ -1,63 +0,0 @@
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Helvetica, Arial, Sans;
|
||||
background: #EEEEEE;
|
||||
}
|
||||
.block {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
#header h1 {
|
||||
font-weight: normal;
|
||||
font-size: 28px;
|
||||
margin: 0;
|
||||
}
|
||||
#more {
|
||||
color: #666;
|
||||
font-size: 80%;
|
||||
border: none;
|
||||
}
|
||||
#header {
|
||||
background: #FFFFCC;
|
||||
}
|
||||
#header p {
|
||||
color: #333;
|
||||
}
|
||||
#routes {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
#routes h2 {
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
#routes ol {
|
||||
|
||||
}
|
||||
#routes li {
|
||||
font-size: 14px;
|
||||
font-family: monospace;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="header" class="block">
|
||||
{{with .Error}}
|
||||
<h1>
|
||||
{{.Title}}
|
||||
</h1>
|
||||
<p>
|
||||
{{.Description}}
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
<div id="routes" class="block">
|
||||
<h2>These routes have been tried, in this order :</h2>
|
||||
<ol>
|
||||
{{range .Router.Routes}}
|
||||
<li>{{pad .Method 10}}{{pad .Path 50}}{{.Action}}</li>
|
||||
{{end}}
|
||||
</ol>
|
||||
</div>
|
||||
@@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Not found</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{{if .DevMode}}
|
||||
|
||||
{{template "errors/404-dev.html" .}}
|
||||
|
||||
{{else}}
|
||||
|
||||
{{with .Error}}
|
||||
<h1>
|
||||
{{.Title}}
|
||||
</h1>
|
||||
<p>
|
||||
{{.Description}}
|
||||
</p>
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"title": "{{js .Error.Title}}",
|
||||
"description": "{{js .Error.Description}}"
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{{.Error.Title}}
|
||||
|
||||
{{.Error.Description}}
|
||||
@@ -1 +0,0 @@
|
||||
<notfound>{{.Error.Description}}</notfound>
|
||||
@@ -1,118 +0,0 @@
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Helvetica, Arial, Sans;
|
||||
background: #EEEEEE;
|
||||
}
|
||||
.block {
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #aaa;
|
||||
}
|
||||
#header h1 {
|
||||
font-weight: normal;
|
||||
font-size: 28px;
|
||||
margin: 0;
|
||||
}
|
||||
#more {
|
||||
color: #666;
|
||||
font-size: 80%;
|
||||
border: none;
|
||||
}
|
||||
#header {
|
||||
background: #fcd2da;
|
||||
}
|
||||
#header p {
|
||||
color: #333;
|
||||
}
|
||||
#source {
|
||||
background: #f6f6f6;
|
||||
}
|
||||
#source h2 {
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
#source .lineNumber {
|
||||
float: left;
|
||||
display: block;
|
||||
width: 40px;
|
||||
text-align: right;
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
font-family: monospace;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
}
|
||||
#source .line {
|
||||
clear: both;
|
||||
color: #333;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
#source pre {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
#source .error {
|
||||
color: #c00 !important;
|
||||
}
|
||||
#source .error .lineNumber {
|
||||
background: #c00;
|
||||
}
|
||||
#source a {
|
||||
text-decoration: none;
|
||||
}
|
||||
#source a:hover * {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
#source a:hover pre {
|
||||
background: #FAFFCF !important;
|
||||
}
|
||||
#source em {
|
||||
font-style: normal;
|
||||
text-decoration: underline;
|
||||
font-weight: bold;
|
||||
}
|
||||
#source strong {
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
{{with .Error}}
|
||||
<div id="header" class="block">
|
||||
<h1>
|
||||
{{.Title}}
|
||||
</h1>
|
||||
<p>
|
||||
{{if .SourceType}}
|
||||
The {{.SourceType}} <strong>{{.Path}}</strong> does not compile: <strong>{{.Description}}</strong>
|
||||
{{else}}
|
||||
{{.Description}}
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
{{if .Path}}
|
||||
<div id="source" class="block">
|
||||
<h2>In {{.Path}}
|
||||
{{if .Line}}
|
||||
(around {{if .Line}}line {{.Line}}{{end}}{{if .Column}} column {{.Column}}{{end}})
|
||||
{{end}}
|
||||
</h2>
|
||||
{{range .ContextSource}}
|
||||
<div class="line {{if .IsError}}error{{end}}">
|
||||
<span class="lineNumber">{{.Line}}:</span>
|
||||
<pre>{{.Source}}</pre>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .MetaError}}
|
||||
<div id="source" class="block">
|
||||
<h2>Additionally, an error occurred while handling this error.</h2>
|
||||
<div class="line error">
|
||||
{{.MetaError}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Application error</title>
|
||||
</head>
|
||||
<body>
|
||||
{{if .DevMode}}
|
||||
{{template "errors/500-dev.html" .}}
|
||||
{{else}}
|
||||
<h1>Oops, an error occured</h1>
|
||||
<p>
|
||||
This exception has been logged.
|
||||
</p>
|
||||
{{end}}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"title": "{{js .Error.Title}}",
|
||||
"description": "{{js .Error.Description}}"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{{.Error.Title}}
|
||||
{{.Error.Description}}
|
||||
|
||||
{{if eq .RunMode "dev"}}
|
||||
{{with .Error}}
|
||||
{{if .Path}}
|
||||
----------
|
||||
In {{.Path}} {{if .Line}}(around line {{.Line}}){{end}}
|
||||
|
||||
{{range .ContextSource}}
|
||||
{{if .IsError}}>{{else}} {{end}} {{.Line}}: {{.Source}}{{end}}
|
||||
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
@@ -1,4 +0,0 @@
|
||||
<error>
|
||||
<title>{{.Error.Title}}</title>
|
||||
<description>{{.Error.Description}}</description>
|
||||
</error>
|
||||
@@ -1,11 +0,0 @@
|
||||
# Revel command line tools
|
||||
|
||||
Provides the `revel` command, used to create and run Revel apps.
|
||||
|
||||
- More info at http://revel.github.io/manual/tool.html
|
||||
|
||||
Install
|
||||
------------
|
||||
```bash
|
||||
go get github.com/revel/cmd/revel
|
||||
```
|
||||
@@ -1,128 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
// App contains the configuration for running a Revel app. (Not for the app itself)
|
||||
// Its only purpose is constructing the command to execute.
|
||||
type App struct {
|
||||
BinaryPath string // Path to the app executable
|
||||
Port int // Port to pass as a command line argument.
|
||||
cmd AppCmd // The last cmd returned.
|
||||
}
|
||||
|
||||
// NewApp returns app instance with binary path in it
|
||||
func NewApp(binPath string) *App {
|
||||
return &App{BinaryPath: binPath}
|
||||
}
|
||||
|
||||
// Cmd returns a command to run the app server using the current configuration.
|
||||
func (a *App) Cmd() AppCmd {
|
||||
a.cmd = NewAppCmd(a.BinaryPath, a.Port)
|
||||
return a.cmd
|
||||
}
|
||||
|
||||
// Kill the last app command returned.
|
||||
func (a *App) Kill() {
|
||||
a.cmd.Kill()
|
||||
}
|
||||
|
||||
// AppCmd manages the running of a Revel app server.
|
||||
// It requires revel.Init to have been called previously.
|
||||
type AppCmd struct {
|
||||
*exec.Cmd
|
||||
}
|
||||
|
||||
// NewAppCmd returns the AppCmd with parameters initialized for running app
|
||||
func NewAppCmd(binPath string, port int) AppCmd {
|
||||
cmd := exec.Command(binPath,
|
||||
fmt.Sprintf("-port=%d", port),
|
||||
fmt.Sprintf("-importPath=%s", revel.ImportPath),
|
||||
fmt.Sprintf("-runMode=%s", revel.RunMode))
|
||||
cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
|
||||
return AppCmd{cmd}
|
||||
}
|
||||
|
||||
// Start the app server, and wait until it is ready to serve requests.
|
||||
func (cmd AppCmd) Start() error {
|
||||
listeningWriter := &startupListeningWriter{os.Stdout, make(chan bool)}
|
||||
cmd.Stdout = listeningWriter
|
||||
revel.RevelLog.Debug("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||
if err := cmd.Cmd.Start(); err != nil {
|
||||
revel.RevelLog.Fatal("Error running:", "error", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-cmd.waitChan():
|
||||
return errors.New("revel/harness: app died")
|
||||
|
||||
case <-time.After(30 * time.Second):
|
||||
revel.RevelLog.Debug("Killing revel server process did not respond after wait timeout", "processid", cmd.Process.Pid)
|
||||
cmd.Kill()
|
||||
return errors.New("revel/harness: app timed out")
|
||||
|
||||
case <-listeningWriter.notifyReady:
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO remove this unreachable code and document it
|
||||
panic("Impossible")
|
||||
}
|
||||
|
||||
// Run the app server inline. Never returns.
|
||||
func (cmd AppCmd) Run() {
|
||||
revel.RevelLog.Debug("Exec app:", "path", cmd.Path, "args", cmd.Args)
|
||||
if err := cmd.Cmd.Run(); err != nil {
|
||||
revel.RevelLog.Fatal("Error running:", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Kill terminates the app server if it's running.
|
||||
func (cmd AppCmd) Kill() {
|
||||
if cmd.Cmd != nil && (cmd.ProcessState == nil || !cmd.ProcessState.Exited()) {
|
||||
revel.RevelLog.Debug("Killing revel server pid", "pid", cmd.Process.Pid)
|
||||
err := cmd.Process.Kill()
|
||||
if err != nil {
|
||||
revel.RevelLog.Fatal("Failed to kill revel server:", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return a channel that is notified when Wait() returns.
|
||||
func (cmd AppCmd) waitChan() <-chan struct{} {
|
||||
ch := make(chan struct{}, 1)
|
||||
go func() {
|
||||
_ = cmd.Wait()
|
||||
ch <- struct{}{}
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// A io.Writer that copies to the destination, and listens for "Listening on.."
|
||||
// in the stream. (Which tells us when the revel server has finished starting up)
|
||||
// This is super ghetto, but by far the simplest thing that should work.
|
||||
type startupListeningWriter struct {
|
||||
dest io.Writer
|
||||
notifyReady chan bool
|
||||
}
|
||||
|
||||
func (w *startupListeningWriter) Write(p []byte) (n int, err error) {
|
||||
if w.notifyReady != nil && bytes.Contains(p, []byte("Listening")) {
|
||||
w.notifyReady <- true
|
||||
w.notifyReady = nil
|
||||
}
|
||||
return w.dest.Write(p)
|
||||
}
|
||||
@@ -1,491 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var importErrorPattern = regexp.MustCompile("cannot find package \"([^\"]+)\"")
|
||||
|
||||
// Build the app:
|
||||
// 1. Generate the the main.go file.
|
||||
// 2. Run the appropriate "go build" command.
|
||||
// Requires that revel.Init has been called previously.
|
||||
// Returns the path to the built binary, and an error if there was a problem building it.
|
||||
func Build(buildFlags ...string) (app *App, compileError *revel.Error) {
|
||||
// First, clear the generated files (to avoid them messing with ProcessSource).
|
||||
cleanSource("tmp", "routes")
|
||||
|
||||
sourceInfo, compileError := ProcessSource(revel.CodePaths)
|
||||
if compileError != nil {
|
||||
return nil, compileError
|
||||
}
|
||||
|
||||
// Add the db.import to the import paths.
|
||||
if dbImportPath, found := revel.Config.String("db.import"); found {
|
||||
sourceInfo.InitImportPaths = append(sourceInfo.InitImportPaths, strings.Split(dbImportPath,",")...)
|
||||
}
|
||||
|
||||
// Generate two source files.
|
||||
templateArgs := map[string]interface{}{
|
||||
"Controllers": sourceInfo.ControllerSpecs(),
|
||||
"ValidationKeys": sourceInfo.ValidationKeys,
|
||||
"ImportPaths": calcImportAliases(sourceInfo),
|
||||
"TestSuites": sourceInfo.TestSuites(),
|
||||
}
|
||||
genSource("tmp", "main.go", RevelMainTemplate, templateArgs)
|
||||
genSource("routes", "routes.go", RevelRoutesTemplate, templateArgs)
|
||||
|
||||
// Read build config.
|
||||
buildTags := revel.Config.StringDefault("build.tags", "")
|
||||
|
||||
// Build the user program (all code under app).
|
||||
// It relies on the user having "go" installed.
|
||||
goPath, err := exec.LookPath("go")
|
||||
if err != nil {
|
||||
revel.RevelLog.Fatalf("Go executable not found in PATH.")
|
||||
}
|
||||
|
||||
// Detect if deps tool should be used (is there a vendor folder ?)
|
||||
useVendor := revel.DirExists(filepath.Join(revel.BasePath, "vendor"))
|
||||
basePath := revel.BasePath
|
||||
for !useVendor {
|
||||
basePath = filepath.Dir(basePath)
|
||||
found := false
|
||||
// Check to see if we are still in the GOPATH
|
||||
for _, path := range filepath.SplitList(build.Default.GOPATH) {
|
||||
if strings.HasPrefix(basePath, path) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
break
|
||||
} else {
|
||||
useVendor = revel.DirExists(filepath.Join(basePath, "vendor"))
|
||||
}
|
||||
}
|
||||
|
||||
var depPath string
|
||||
if useVendor {
|
||||
revel.RevelLog.Info("Vendor folder detected, scanning for deps in path")
|
||||
depPath, err = exec.LookPath("dep")
|
||||
if err != nil {
|
||||
// Do not halt build unless a new package needs to be imported
|
||||
revel.RevelLog.Warn("Build: `dep` executable not found in PATH, but vendor folder detected." +
|
||||
"Packages can only be added automatically to the vendor folder using the `dep` tool. " +
|
||||
"You can install the `dep` tool by doing a `go get -u github.com/golang/dep/cmd/dep`")
|
||||
}
|
||||
} else {
|
||||
revel.RevelLog.Info("No vendor folder detected, not using dependency manager to import files")
|
||||
}
|
||||
|
||||
pkg, err := build.Default.Import(revel.ImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
revel.RevelLog.Fatal("Failure importing", "path", revel.ImportPath)
|
||||
}
|
||||
|
||||
// Binary path is a combination of $GOBIN/revel.d directory, app's import path and its name.
|
||||
binName := filepath.Join(pkg.BinDir, "revel.d", revel.ImportPath, filepath.Base(revel.BasePath))
|
||||
|
||||
// Change binary path for Windows build
|
||||
goos := runtime.GOOS
|
||||
if goosEnv := os.Getenv("GOOS"); goosEnv != "" {
|
||||
goos = goosEnv
|
||||
}
|
||||
if goos == "windows" {
|
||||
binName += ".exe"
|
||||
}
|
||||
|
||||
gotten := make(map[string]struct{})
|
||||
for {
|
||||
appVersion := getAppVersion()
|
||||
|
||||
buildTime := time.Now().UTC().Format(time.RFC3339)
|
||||
versionLinkerFlags := fmt.Sprintf("-X %s/app.AppVersion=%s -X %s/app.BuildTime=%s",
|
||||
revel.ImportPath, appVersion, revel.ImportPath, buildTime)
|
||||
|
||||
flags := []string{
|
||||
"build",
|
||||
"-i",
|
||||
"-ldflags", versionLinkerFlags,
|
||||
"-tags", buildTags,
|
||||
"-o", binName}
|
||||
|
||||
// Add in build flags
|
||||
flags = append(flags, buildFlags...)
|
||||
|
||||
// This is Go main path
|
||||
// Note: It's not applicable for filepath.* usage
|
||||
flags = append(flags, path.Join(revel.ImportPath, "app", "tmp"))
|
||||
|
||||
buildCmd := exec.Command(goPath, flags...)
|
||||
revel.RevelLog.Debug("Exec:", "args", buildCmd.Args)
|
||||
output, err := buildCmd.CombinedOutput()
|
||||
|
||||
// If the build succeeded, we're done.
|
||||
if err == nil {
|
||||
return NewApp(binName), nil
|
||||
}
|
||||
revel.RevelLog.Error(string(output))
|
||||
|
||||
// See if it was an import error that we can go get.
|
||||
matches := importErrorPattern.FindAllStringSubmatch(string(output), -1)
|
||||
if matches == nil {
|
||||
return nil, newCompileError(output)
|
||||
}
|
||||
for _, match := range matches {
|
||||
// Ensure we haven't already tried to go get it.
|
||||
pkgName := match[1]
|
||||
if _, alreadyTried := gotten[pkgName]; alreadyTried {
|
||||
return nil, newCompileError(output)
|
||||
}
|
||||
gotten[pkgName] = struct{}{}
|
||||
|
||||
// Execute "go get <pkg>"
|
||||
// Or dep `dep ensure -add <pkg>` if it is there
|
||||
var getCmd *exec.Cmd
|
||||
if useVendor {
|
||||
if depPath == "" {
|
||||
revel.RevelLog.Error("Build: Vendor folder found, but the `dep` tool was not found, " +
|
||||
"if you use a different vendoring (package management) tool please add the following packages by hand, " +
|
||||
"or install the `dep` tool into your gopath by doing a `go get -u github.com/golang/dep/cmd/dep`. " +
|
||||
"For more information and usage of the tool please see http://github.com/golang/dep")
|
||||
for _, pkg := range matches {
|
||||
revel.RevelLog.Error("Missing package", "package", pkg[1])
|
||||
}
|
||||
}
|
||||
getCmd = exec.Command(depPath, "ensure", "-add", pkgName)
|
||||
} else {
|
||||
getCmd = exec.Command(goPath, "get", pkgName)
|
||||
}
|
||||
revel.RevelLog.Debug("Exec:", "args", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
revel.RevelLog.Error(string(getOutput))
|
||||
return nil, newCompileError(output)
|
||||
}
|
||||
}
|
||||
|
||||
// Success getting the import, attempt to build again.
|
||||
}
|
||||
|
||||
// TODO remove this unreachable code and document it
|
||||
revel.RevelLog.Fatalf("Not reachable")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Try to define a version string for the compiled app
|
||||
// The following is tried (first match returns):
|
||||
// - Read a version explicitly specified in the APP_VERSION environment
|
||||
// variable
|
||||
// - Read the output of "git describe" if the source is in a git repository
|
||||
// If no version can be determined, an empty string is returned.
|
||||
func getAppVersion() string {
|
||||
if version := os.Getenv("APP_VERSION"); version != "" {
|
||||
return version
|
||||
}
|
||||
|
||||
// Check for the git binary
|
||||
if gitPath, err := exec.LookPath("git"); err == nil {
|
||||
// Check for the .git directory
|
||||
gitDir := filepath.Join(revel.BasePath, ".git")
|
||||
info, err := os.Stat(gitDir)
|
||||
if (err != nil && os.IsNotExist(err)) || !info.IsDir() {
|
||||
return ""
|
||||
}
|
||||
gitCmd := exec.Command(gitPath, "--git-dir="+gitDir, "describe", "--always", "--dirty")
|
||||
revel.RevelLog.Debug("Exec:", "args", gitCmd.Args)
|
||||
output, err := gitCmd.Output()
|
||||
|
||||
if err != nil {
|
||||
revel.RevelLog.Warn("Cannot determine git repository version:", "error", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return "git-" + strings.TrimSpace(string(output))
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func cleanSource(dirs ...string) {
|
||||
for _, dir := range dirs {
|
||||
cleanDir(dir)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanDir(dir string) {
|
||||
revel.RevelLog.Info("Cleaning dir " + dir)
|
||||
tmpPath := filepath.Join(revel.AppPath, dir)
|
||||
f, err := os.Open(tmpPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
revel.RevelLog.Error("Failed to clean dir:", "error", err)
|
||||
}
|
||||
} else {
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
infos, err := f.Readdir(0)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
revel.RevelLog.Error("Failed to clean dir:", "error", err)
|
||||
}
|
||||
} else {
|
||||
for _, info := range infos {
|
||||
pathName := filepath.Join(tmpPath, info.Name())
|
||||
if info.IsDir() {
|
||||
err := os.RemoveAll(pathName)
|
||||
if err != nil {
|
||||
revel.RevelLog.Error("Failed to remove dir:", "error", err)
|
||||
}
|
||||
} else {
|
||||
err := os.Remove(pathName)
|
||||
if err != nil {
|
||||
revel.RevelLog.Error("Failed to remove file:", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// genSource renders the given template to produce source code, which it writes
|
||||
// to the given directory and file.
|
||||
func genSource(dir, filename, templateSource string, args map[string]interface{}) {
|
||||
sourceCode := revel.ExecuteTemplate(
|
||||
template.Must(template.New("").Parse(templateSource)),
|
||||
args)
|
||||
|
||||
// Create a fresh dir.
|
||||
cleanSource(dir)
|
||||
tmpPath := filepath.Join(revel.AppPath, dir)
|
||||
err := os.Mkdir(tmpPath, 0777)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
revel.RevelLog.Fatalf("Failed to make '%v' directory: %v", dir, err)
|
||||
}
|
||||
|
||||
// Create the file
|
||||
file, err := os.Create(filepath.Join(tmpPath, filename))
|
||||
if err != nil {
|
||||
revel.RevelLog.Fatalf("Failed to create file: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
if _, err = file.WriteString(sourceCode); err != nil {
|
||||
revel.RevelLog.Fatalf("Failed to write to file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Looks through all the method args and returns a set of unique import paths
|
||||
// that cover all the method arg types.
|
||||
// Additionally, assign package aliases when necessary to resolve ambiguity.
|
||||
func calcImportAliases(src *SourceInfo) map[string]string {
|
||||
aliases := make(map[string]string)
|
||||
typeArrays := [][]*TypeInfo{src.ControllerSpecs(), src.TestSuites()}
|
||||
for _, specs := range typeArrays {
|
||||
for _, spec := range specs {
|
||||
addAlias(aliases, spec.ImportPath, spec.PackageName)
|
||||
|
||||
for _, methSpec := range spec.MethodSpecs {
|
||||
for _, methArg := range methSpec.Args {
|
||||
if methArg.ImportPath == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
addAlias(aliases, methArg.ImportPath, methArg.TypeExpr.PkgName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the "InitImportPaths", with alias "_"
|
||||
for _, importPath := range src.InitImportPaths {
|
||||
if _, ok := aliases[importPath]; !ok {
|
||||
aliases[importPath] = "_"
|
||||
}
|
||||
}
|
||||
|
||||
return aliases
|
||||
}
|
||||
|
||||
func addAlias(aliases map[string]string, importPath, pkgName string) {
|
||||
alias, ok := aliases[importPath]
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
alias = makePackageAlias(aliases, pkgName)
|
||||
aliases[importPath] = alias
|
||||
}
|
||||
|
||||
func makePackageAlias(aliases map[string]string, pkgName string) string {
|
||||
i := 0
|
||||
alias := pkgName
|
||||
for containsValue(aliases, alias) || alias == "revel" {
|
||||
alias = fmt.Sprintf("%s%d", pkgName, i)
|
||||
i++
|
||||
}
|
||||
return alias
|
||||
}
|
||||
|
||||
func containsValue(m map[string]string, val string) bool {
|
||||
for _, v := range m {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse the output of the "go build" command.
|
||||
// Return a detailed Error.
|
||||
func newCompileError(output []byte) *revel.Error {
|
||||
errorMatch := regexp.MustCompile(`(?m)^([^:#]+):(\d+):(\d+:)? (.*)$`).
|
||||
FindSubmatch(output)
|
||||
if errorMatch == nil {
|
||||
errorMatch = regexp.MustCompile(`(?m)^(.*?)\:(\d+)\:\s(.*?)$`).FindSubmatch(output)
|
||||
|
||||
if errorMatch == nil {
|
||||
revel.RevelLog.Error("Failed to parse build errors", "error", string(output))
|
||||
return &revel.Error{
|
||||
SourceType: "Go code",
|
||||
Title: "Go Compilation Error",
|
||||
Description: "See console for build error.",
|
||||
}
|
||||
}
|
||||
|
||||
errorMatch = append(errorMatch, errorMatch[3])
|
||||
|
||||
revel.RevelLog.Error("Build errors", "errors", string(output))
|
||||
}
|
||||
|
||||
// Read the source for the offending file.
|
||||
var (
|
||||
relFilename = string(errorMatch[1]) // e.g. "src/revel/sample/app/controllers/app.go"
|
||||
absFilename, _ = filepath.Abs(relFilename)
|
||||
line, _ = strconv.Atoi(string(errorMatch[2]))
|
||||
description = string(errorMatch[4])
|
||||
compileError = &revel.Error{
|
||||
SourceType: "Go code",
|
||||
Title: "Go Compilation Error",
|
||||
Path: relFilename,
|
||||
Description: description,
|
||||
Line: line,
|
||||
}
|
||||
)
|
||||
|
||||
errorLink := revel.Config.StringDefault("error.link", "")
|
||||
|
||||
if errorLink != "" {
|
||||
compileError.SetLink(errorLink)
|
||||
}
|
||||
|
||||
fileStr, err := revel.ReadLines(absFilename)
|
||||
if err != nil {
|
||||
compileError.MetaError = absFilename + ": " + err.Error()
|
||||
revel.RevelLog.Error(compileError.MetaError)
|
||||
return compileError
|
||||
}
|
||||
|
||||
compileError.SourceLines = fileStr
|
||||
return compileError
|
||||
}
|
||||
|
||||
// RevelMainTemplate template for app/tmp/main.go
|
||||
const RevelMainTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"reflect"
|
||||
"github.com/revel/revel"{{range $k, $v := $.ImportPaths}}
|
||||
{{$v}} "{{$k}}"{{end}}
|
||||
"github.com/revel/revel/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
runMode *string = flag.String("runMode", "", "Run mode.")
|
||||
port *int = flag.Int("port", 0, "By default, read from app.conf")
|
||||
importPath *string = flag.String("importPath", "", "Go Import Path for the app.")
|
||||
srcPath *string = flag.String("srcPath", "", "Path to the source root.")
|
||||
|
||||
// So compiler won't complain if the generated code doesn't reference reflect package...
|
||||
_ = reflect.Invalid
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
revel.Init(*runMode, *importPath, *srcPath)
|
||||
revel.AppLog.Info("Running revel server")
|
||||
{{range $i, $c := .Controllers}}
|
||||
revel.RegisterController((*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),
|
||||
[]*revel.MethodType{
|
||||
{{range .MethodSpecs}}&revel.MethodType{
|
||||
Name: "{{.Name}}",
|
||||
Args: []*revel.MethodArg{ {{range .Args}}
|
||||
&revel.MethodArg{Name: "{{.Name}}", Type: reflect.TypeOf((*{{index $.ImportPaths .ImportPath | .TypeExpr.TypeName}})(nil)) },{{end}}
|
||||
},
|
||||
RenderArgNames: map[int][]string{ {{range .RenderCalls}}
|
||||
{{.Line}}: []string{ {{range .Names}}
|
||||
"{{.}}",{{end}}
|
||||
},{{end}}
|
||||
},
|
||||
},
|
||||
{{end}}
|
||||
})
|
||||
{{end}}
|
||||
revel.DefaultValidationKeys = map[string]map[int]string{ {{range $path, $lines := .ValidationKeys}}
|
||||
"{{$path}}": { {{range $line, $key := $lines}}
|
||||
{{$line}}: "{{$key}}",{{end}}
|
||||
},{{end}}
|
||||
}
|
||||
testing.TestSuites = []interface{}{ {{range .TestSuites}}
|
||||
(*{{index $.ImportPaths .ImportPath}}.{{.StructName}})(nil),{{end}}
|
||||
}
|
||||
|
||||
revel.Run(*port)
|
||||
}
|
||||
`
|
||||
|
||||
// RevelRoutesTemplate template for app/conf/routes
|
||||
const RevelRoutesTemplate = `// GENERATED CODE - DO NOT EDIT
|
||||
package routes
|
||||
|
||||
import "github.com/revel/revel"
|
||||
|
||||
{{range $i, $c := .Controllers}}
|
||||
type t{{.StructName}} struct {}
|
||||
var {{.StructName}} t{{.StructName}}
|
||||
|
||||
{{range .MethodSpecs}}
|
||||
func (_ t{{$c.StructName}}) {{.Name}}({{range .Args}}
|
||||
{{.Name}} {{if .ImportPath}}interface{}{{else}}{{.TypeExpr.TypeName ""}}{{end}},{{end}}
|
||||
) string {
|
||||
args := make(map[string]string)
|
||||
{{range .Args}}
|
||||
revel.Unbind(args, "{{.Name}}", {{.Name}}){{end}}
|
||||
return revel.MainRouter.Reverse("{{$c.StructName}}.{{.Name}}", args).URL
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
`
|
||||
@@ -1,285 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package harness for a Revel Framework.
|
||||
//
|
||||
// It has a following responsibilities:
|
||||
// 1. Parse the user program, generating a main.go file that registers
|
||||
// controller classes and starts the user's server.
|
||||
// 2. Build and run the user program. Show compile errors.
|
||||
// 3. Monitor the user source and re-build / restart the program when necessary.
|
||||
//
|
||||
// Source files are generated in the app/tmp directory.
|
||||
package harness
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/revel/revel"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
doNotWatch = []string{"tmp", "views", "routes"}
|
||||
|
||||
lastRequestHadError int32
|
||||
)
|
||||
|
||||
// Harness reverse proxies requests to the application server.
|
||||
// It builds / runs / rebuilds / restarts the server when code is changed.
|
||||
type Harness struct {
|
||||
app *App
|
||||
serverHost string
|
||||
port int
|
||||
proxy *httputil.ReverseProxy
|
||||
watcher *revel.Watcher
|
||||
mutex *sync.Mutex
|
||||
}
|
||||
|
||||
func renderError(iw http.ResponseWriter, ir *http.Request, err error) {
|
||||
context := revel.NewGoContext(nil)
|
||||
context.Request.SetRequest(ir)
|
||||
context.Response.SetResponse(iw)
|
||||
c := revel.NewController(context)
|
||||
c.RenderError(err).Apply(c.Request, c.Response)
|
||||
}
|
||||
|
||||
// ServeHTTP handles all requests.
|
||||
// It checks for changes to app, rebuilds if necessary, and forwards the request.
|
||||
func (h *Harness) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Don't rebuild the app for favicon requests.
|
||||
if lastRequestHadError > 0 && r.URL.Path == "/favicon.ico" {
|
||||
return
|
||||
}
|
||||
|
||||
// Flush any change events and rebuild app if necessary.
|
||||
// Render an error page if the rebuild / restart failed.
|
||||
err := h.watcher.Notify()
|
||||
if err != nil {
|
||||
// In a thread safe manner update the flag so that a request for
|
||||
// /favicon.ico does not trigger a rebuild
|
||||
atomic.CompareAndSwapInt32(&lastRequestHadError, 0, 1)
|
||||
renderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// In a thread safe manner update the flag so that a request for
|
||||
// /favicon.ico is allowed
|
||||
atomic.CompareAndSwapInt32(&lastRequestHadError, 1, 0)
|
||||
|
||||
// Reverse proxy the request.
|
||||
// (Need special code for websockets, courtesy of bradfitz)
|
||||
if strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
||||
proxyWebsocket(w, r, h.serverHost)
|
||||
} else {
|
||||
h.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// NewHarness method returns a reverse proxy that forwards requests
|
||||
// to the given port.
|
||||
func NewHarness() *Harness {
|
||||
// Get a template loader to render errors.
|
||||
// Prefer the app's views/errors directory, and fall back to the stock error pages.
|
||||
revel.MainTemplateLoader = revel.NewTemplateLoader(
|
||||
[]string{filepath.Join(revel.RevelPath, "templates")})
|
||||
if err := revel.MainTemplateLoader.Refresh(); err != nil {
|
||||
revel.RevelLog.Error("Template loader error", "error", err)
|
||||
}
|
||||
|
||||
addr := revel.HTTPAddr
|
||||
port := revel.Config.IntDefault("harness.port", 0)
|
||||
scheme := "http"
|
||||
if revel.HTTPSsl {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
// If the server is running on the wildcard address, use "localhost"
|
||||
if addr == "" {
|
||||
addr = "localhost"
|
||||
}
|
||||
|
||||
if port == 0 {
|
||||
port = getFreePort()
|
||||
}
|
||||
|
||||
serverURL, _ := url.ParseRequestURI(fmt.Sprintf(scheme+"://%s:%d", addr, port))
|
||||
|
||||
serverHarness := &Harness{
|
||||
port: port,
|
||||
serverHost: serverURL.String()[len(scheme+"://"):],
|
||||
proxy: httputil.NewSingleHostReverseProxy(serverURL),
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
|
||||
if revel.HTTPSsl {
|
||||
serverHarness.proxy.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
return serverHarness
|
||||
}
|
||||
|
||||
// Refresh method rebuilds the Revel application and run it on the given port.
|
||||
func (h *Harness) Refresh() (err *revel.Error) {
|
||||
// Allow only one thread to rebuild the process
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
if h.app != nil {
|
||||
h.app.Kill()
|
||||
}
|
||||
|
||||
revel.RevelLog.Debug("Rebuild Called")
|
||||
h.app, err = Build()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h.app.Port = h.port
|
||||
if err2 := h.app.Cmd().Start(); err2 != nil {
|
||||
return &revel.Error{
|
||||
Title: "App failed to start up",
|
||||
Description: err2.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WatchDir method returns false to file matches with doNotWatch
|
||||
// otheriwse true
|
||||
func (h *Harness) WatchDir(info os.FileInfo) bool {
|
||||
return !revel.ContainsString(doNotWatch, info.Name())
|
||||
}
|
||||
|
||||
// WatchFile method returns true given filename HasSuffix of ".go"
|
||||
// otheriwse false - implements revel.DiscerningListener
|
||||
func (h *Harness) WatchFile(filename string) bool {
|
||||
return strings.HasSuffix(filename, ".go")
|
||||
}
|
||||
|
||||
// Run the harness, which listens for requests and proxies them to the app
|
||||
// server, which it runs and rebuilds as necessary.
|
||||
func (h *Harness) Run() {
|
||||
var paths []string
|
||||
if revel.Config.BoolDefault("watch.gopath", false) {
|
||||
gopaths := filepath.SplitList(build.Default.GOPATH)
|
||||
paths = append(paths, gopaths...)
|
||||
}
|
||||
paths = append(paths, revel.CodePaths...)
|
||||
h.watcher = revel.NewWatcher()
|
||||
h.watcher.Listen(h, paths...)
|
||||
h.watcher.Notify()
|
||||
|
||||
go func() {
|
||||
addr := fmt.Sprintf("%s:%d", revel.HTTPAddr, revel.HTTPPort)
|
||||
revel.RevelLog.Infof("Listening on %s", addr)
|
||||
|
||||
var err error
|
||||
if revel.HTTPSsl {
|
||||
err = http.ListenAndServeTLS(
|
||||
addr,
|
||||
revel.HTTPSslCert,
|
||||
revel.HTTPSslKey,
|
||||
h)
|
||||
} else {
|
||||
err = http.ListenAndServe(addr, h)
|
||||
}
|
||||
if err != nil {
|
||||
revel.RevelLog.Error("Failed to start reverse proxy:", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Kill the app on signal.
|
||||
ch := make(chan os.Signal)
|
||||
signal.Notify(ch, os.Interrupt, os.Kill)
|
||||
<-ch
|
||||
if h.app != nil {
|
||||
h.app.Kill()
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Find an unused port
|
||||
func getFreePort() (port int) {
|
||||
conn, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
revel.RevelLog.Fatal("Unable to fetch a freee port address", "error", err)
|
||||
}
|
||||
|
||||
port = conn.Addr().(*net.TCPAddr).Port
|
||||
err = conn.Close()
|
||||
if err != nil {
|
||||
revel.RevelLog.Fatal("Unable to close port", "error", err)
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
// proxyWebsocket copies data between websocket client and server until one side
|
||||
// closes the connection. (ReverseProxy doesn't work with websocket requests.)
|
||||
func proxyWebsocket(w http.ResponseWriter, r *http.Request, host string) {
|
||||
var (
|
||||
d net.Conn
|
||||
err error
|
||||
)
|
||||
if revel.HTTPSsl {
|
||||
// since this proxy isn't used in production,
|
||||
// it's OK to set InsecureSkipVerify to true
|
||||
// no need to add another configuration option.
|
||||
d, err = tls.Dial("tcp", host, &tls.Config{InsecureSkipVerify: true})
|
||||
} else {
|
||||
d, err = net.Dial("tcp", host)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(w, "Error contacting backend server.", 500)
|
||||
revel.RevelLog.Error("Error dialing websocket backend ", "host", host, "error", err)
|
||||
return
|
||||
}
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
http.Error(w, "Not a hijacker?", 500)
|
||||
return
|
||||
}
|
||||
nc, _, err := hj.Hijack()
|
||||
if err != nil {
|
||||
revel.RevelLog.Error("Hijack error", "error", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err = nc.Close(); err != nil {
|
||||
revel.RevelLog.Error("Connection close error", "error", err)
|
||||
}
|
||||
if err = d.Close(); err != nil {
|
||||
revel.RevelLog.Error("Dial close error", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = r.Write(d)
|
||||
if err != nil {
|
||||
revel.RevelLog.Error("Error copying request to target", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
errc := make(chan error, 2)
|
||||
cp := func(dst io.Writer, src io.Reader) {
|
||||
_, err := io.Copy(dst, src)
|
||||
errc <- err
|
||||
}
|
||||
go cp(d, nc)
|
||||
go cp(nc, d)
|
||||
<-errc
|
||||
}
|
||||
@@ -1,843 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
// This file handles the app code introspection.
|
||||
// It catalogs the controllers, their methods, and their arguments.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/revel/revel"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// SourceInfo is the top-level struct containing all extracted information
|
||||
// about the app source code, used to generate main.go.
|
||||
type SourceInfo struct {
|
||||
// StructSpecs lists type info for all structs found under the code paths.
|
||||
// They may be queried to determine which ones (transitively) embed certain types.
|
||||
StructSpecs []*TypeInfo
|
||||
// ValidationKeys provides a two-level lookup. The keys are:
|
||||
// 1. The fully-qualified function name,
|
||||
// e.g. "github.com/revel/examples/chat/app/controllers.(*Application).Action"
|
||||
// 2. Within that func's file, the line number of the (overall) expression statement.
|
||||
// e.g. the line returned from runtime.Caller()
|
||||
// The result of the lookup the name of variable being validated.
|
||||
ValidationKeys map[string]map[int]string
|
||||
// A list of import paths.
|
||||
// Revel notices files with an init() function and imports that package.
|
||||
InitImportPaths []string
|
||||
|
||||
// controllerSpecs lists type info for all structs found under
|
||||
// app/controllers/... that embed (directly or indirectly) revel.Controller
|
||||
controllerSpecs []*TypeInfo
|
||||
// testSuites list the types that constitute the set of application tests.
|
||||
testSuites []*TypeInfo
|
||||
}
|
||||
|
||||
// TypeInfo summarizes information about a struct type in the app source code.
|
||||
type TypeInfo struct {
|
||||
StructName string // e.g. "Application"
|
||||
ImportPath string // e.g. "github.com/revel/examples/chat/app/controllers"
|
||||
PackageName string // e.g. "controllers"
|
||||
MethodSpecs []*MethodSpec
|
||||
|
||||
// Used internally to identify controllers that indirectly embed *revel.Controller.
|
||||
embeddedTypes []*embeddedTypeName
|
||||
}
|
||||
|
||||
// methodCall describes a call to c.Render(..)
|
||||
// It documents the argument names used, in order to propagate them to RenderArgs.
|
||||
type methodCall struct {
|
||||
Path string // e.g. "myapp/app/controllers.(*Application).Action"
|
||||
Line int
|
||||
Names []string
|
||||
}
|
||||
|
||||
// MethodSpec holds the information of one Method
|
||||
type MethodSpec struct {
|
||||
Name string // Name of the method, e.g. "Index"
|
||||
Args []*MethodArg // Argument descriptors
|
||||
RenderCalls []*methodCall // Descriptions of Render() invocations from this Method.
|
||||
}
|
||||
|
||||
// MethodArg holds the information of one argument
|
||||
type MethodArg struct {
|
||||
Name string // Name of the argument.
|
||||
TypeExpr TypeExpr // The name of the type, e.g. "int", "*pkg.UserType"
|
||||
ImportPath string // If the arg is of an imported type, this is the import path.
|
||||
}
|
||||
|
||||
type embeddedTypeName struct {
|
||||
ImportPath, StructName string
|
||||
}
|
||||
|
||||
// Maps a controller simple name (e.g. "Login") to the methods for which it is a
|
||||
// receiver.
|
||||
type methodMap map[string][]*MethodSpec
|
||||
|
||||
// ProcessSource parses the app controllers directory and
|
||||
// returns a list of the controller types found.
|
||||
// Otherwise CompileError if the parsing fails.
|
||||
func ProcessSource(roots []string) (*SourceInfo, *revel.Error) {
|
||||
var (
|
||||
srcInfo *SourceInfo
|
||||
compileError *revel.Error
|
||||
)
|
||||
|
||||
for _, root := range roots {
|
||||
rootImportPath := importPathFromPath(root)
|
||||
if rootImportPath == "" {
|
||||
revel.RevelLog.Warn("Skipping empty code path", "path", root)
|
||||
continue
|
||||
}
|
||||
|
||||
// Start walking the directory tree.
|
||||
_ = revel.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
revel.RevelLog.Error("Error scanning app source:", "error", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if !info.IsDir() || info.Name() == "tmp" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get the import path of the package.
|
||||
pkgImportPath := rootImportPath
|
||||
if root != path {
|
||||
pkgImportPath = rootImportPath + "/" + filepath.ToSlash(path[len(root)+1:])
|
||||
}
|
||||
|
||||
// Parse files within the path.
|
||||
var pkgs map[string]*ast.Package
|
||||
fset := token.NewFileSet()
|
||||
pkgs, err = parser.ParseDir(fset, path, func(f os.FileInfo) bool {
|
||||
return !f.IsDir() && !strings.HasPrefix(f.Name(), ".") && strings.HasSuffix(f.Name(), ".go")
|
||||
}, 0)
|
||||
if err != nil {
|
||||
if errList, ok := err.(scanner.ErrorList); ok {
|
||||
var pos = errList[0].Pos
|
||||
compileError = &revel.Error{
|
||||
SourceType: ".go source",
|
||||
Title: "Go Compilation Error",
|
||||
Path: pos.Filename,
|
||||
Description: errList[0].Msg,
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
SourceLines: revel.MustReadLines(pos.Filename),
|
||||
}
|
||||
|
||||
errorLink := revel.Config.StringDefault("error.link", "")
|
||||
|
||||
if errorLink != "" {
|
||||
compileError.SetLink(errorLink)
|
||||
}
|
||||
|
||||
return compileError
|
||||
}
|
||||
|
||||
// This is exception, err alredy checked above. Here just a print
|
||||
ast.Print(nil, err)
|
||||
revel.RevelLog.Fatal("Failed to parse dir", "error", err)
|
||||
}
|
||||
|
||||
// Skip "main" packages.
|
||||
delete(pkgs, "main")
|
||||
|
||||
// If there is no code in this directory, skip it.
|
||||
if len(pkgs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore packages that end with _test
|
||||
for i := range pkgs {
|
||||
if len(i) > 6 {
|
||||
if string(i[len(i)-5:]) == "_test" {
|
||||
delete(pkgs, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// There should be only one package in this directory.
|
||||
if len(pkgs) > 1 {
|
||||
for i := range pkgs {
|
||||
println("Found package ", i)
|
||||
}
|
||||
revel.RevelLog.Error("Most unexpected! Multiple packages in a single directory:", "packages", pkgs)
|
||||
}
|
||||
|
||||
var pkg *ast.Package
|
||||
for _, v := range pkgs {
|
||||
pkg = v
|
||||
}
|
||||
|
||||
srcInfo = appendSourceInfo(srcInfo, processPackage(fset, pkgImportPath, path, pkg))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
return srcInfo, compileError
|
||||
}
|
||||
|
||||
func appendSourceInfo(srcInfo1, srcInfo2 *SourceInfo) *SourceInfo {
|
||||
if srcInfo1 == nil {
|
||||
return srcInfo2
|
||||
}
|
||||
|
||||
srcInfo1.StructSpecs = append(srcInfo1.StructSpecs, srcInfo2.StructSpecs...)
|
||||
srcInfo1.InitImportPaths = append(srcInfo1.InitImportPaths, srcInfo2.InitImportPaths...)
|
||||
for k, v := range srcInfo2.ValidationKeys {
|
||||
if _, ok := srcInfo1.ValidationKeys[k]; ok {
|
||||
revel.RevelLog.Warn("Key conflict when scanning validation calls:", "key", k)
|
||||
continue
|
||||
}
|
||||
srcInfo1.ValidationKeys[k] = v
|
||||
}
|
||||
return srcInfo1
|
||||
}
|
||||
|
||||
func processPackage(fset *token.FileSet, pkgImportPath, pkgPath string, pkg *ast.Package) *SourceInfo {
|
||||
var (
|
||||
structSpecs []*TypeInfo
|
||||
initImportPaths []string
|
||||
|
||||
methodSpecs = make(methodMap)
|
||||
validationKeys = make(map[string]map[int]string)
|
||||
scanControllers = strings.HasSuffix(pkgImportPath, "/controllers") ||
|
||||
strings.Contains(pkgImportPath, "/controllers/")
|
||||
scanTests = strings.HasSuffix(pkgImportPath, "/tests") ||
|
||||
strings.Contains(pkgImportPath, "/tests/")
|
||||
)
|
||||
|
||||
// For each source file in the package...
|
||||
for _, file := range pkg.Files {
|
||||
|
||||
// Imports maps the package key to the full import path.
|
||||
// e.g. import "sample/app/models" => "models": "sample/app/models"
|
||||
imports := map[string]string{}
|
||||
|
||||
// For each declaration in the source file...
|
||||
for _, decl := range file.Decls {
|
||||
addImports(imports, decl, pkgPath)
|
||||
|
||||
if scanControllers {
|
||||
// Match and add both structs and methods
|
||||
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
||||
appendAction(fset, methodSpecs, decl, pkgImportPath, pkg.Name, imports)
|
||||
} else if scanTests {
|
||||
structSpecs = appendStruct(structSpecs, pkgImportPath, pkg, decl, imports, fset)
|
||||
}
|
||||
|
||||
// If this is a func...
|
||||
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
||||
// Scan it for validation calls
|
||||
lineKeys := getValidationKeys(fset, funcDecl, imports)
|
||||
if len(lineKeys) > 0 {
|
||||
validationKeys[pkgImportPath+"."+getFuncName(funcDecl)] = lineKeys
|
||||
}
|
||||
|
||||
// Check if it's an init function.
|
||||
if funcDecl.Name.Name == "init" {
|
||||
initImportPaths = []string{pkgImportPath}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the method specs to the struct specs.
|
||||
for _, spec := range structSpecs {
|
||||
spec.MethodSpecs = methodSpecs[spec.StructName]
|
||||
}
|
||||
|
||||
return &SourceInfo{
|
||||
StructSpecs: structSpecs,
|
||||
ValidationKeys: validationKeys,
|
||||
InitImportPaths: initImportPaths,
|
||||
}
|
||||
}
|
||||
|
||||
// getFuncName returns a name for this func or method declaration.
|
||||
// e.g. "(*Application).SayHello" for a method, "SayHello" for a func.
|
||||
func getFuncName(funcDecl *ast.FuncDecl) string {
|
||||
prefix := ""
|
||||
if funcDecl.Recv != nil {
|
||||
recvType := funcDecl.Recv.List[0].Type
|
||||
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||
prefix = "(*" + recvStarType.X.(*ast.Ident).Name + ")"
|
||||
} else {
|
||||
prefix = recvType.(*ast.Ident).Name
|
||||
}
|
||||
prefix += "."
|
||||
}
|
||||
return prefix + funcDecl.Name.Name
|
||||
}
|
||||
|
||||
func addImports(imports map[string]string, decl ast.Decl, srcDir string) {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if genDecl.Tok != token.IMPORT {
|
||||
return
|
||||
}
|
||||
|
||||
for _, spec := range genDecl.Specs {
|
||||
importSpec := spec.(*ast.ImportSpec)
|
||||
var pkgAlias string
|
||||
if importSpec.Name != nil {
|
||||
pkgAlias = importSpec.Name.Name
|
||||
if pkgAlias == "_" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
quotedPath := importSpec.Path.Value // e.g. "\"sample/app/models\""
|
||||
fullPath := quotedPath[1 : len(quotedPath)-1] // Remove the quotes
|
||||
|
||||
// If the package was not aliased (common case), we have to import it
|
||||
// to see what the package name is.
|
||||
// TODO: Can improve performance here a lot:
|
||||
// 1. Do not import everything over and over again. Keep a cache.
|
||||
// 2. Exempt the standard library; their directories always match the package name.
|
||||
// 3. Can use build.FindOnly and then use parser.ParseDir with mode PackageClauseOnly
|
||||
if pkgAlias == "" {
|
||||
pkg, err := build.Import(fullPath, srcDir, 0)
|
||||
if err != nil {
|
||||
// We expect this to happen for apps using reverse routing (since we
|
||||
// have not yet generated the routes). Don't log that.
|
||||
if !strings.HasSuffix(fullPath, "/app/routes") {
|
||||
revel.RevelLog.Debug("Could not find import:", "path", fullPath)
|
||||
}
|
||||
continue
|
||||
}
|
||||
pkgAlias = pkg.Name
|
||||
}
|
||||
|
||||
imports[pkgAlias] = fullPath
|
||||
}
|
||||
}
|
||||
|
||||
// If this Decl is a struct type definition, it is summarized and added to specs.
|
||||
// Else, specs is returned unchanged.
|
||||
func appendStruct(specs []*TypeInfo, pkgImportPath string, pkg *ast.Package, decl ast.Decl, imports map[string]string, fset *token.FileSet) []*TypeInfo {
|
||||
// Filter out non-Struct type declarations.
|
||||
spec, found := getStructTypeDecl(decl, fset)
|
||||
if !found {
|
||||
return specs
|
||||
}
|
||||
|
||||
structType := spec.Type.(*ast.StructType)
|
||||
|
||||
// At this point we know it's a type declaration for a struct.
|
||||
// Fill in the rest of the info by diving into the fields.
|
||||
// Add it provisionally to the Controller list -- it's later filtered using field info.
|
||||
controllerSpec := &TypeInfo{
|
||||
StructName: spec.Name.Name,
|
||||
ImportPath: pkgImportPath,
|
||||
PackageName: pkg.Name,
|
||||
}
|
||||
|
||||
for _, field := range structType.Fields.List {
|
||||
// If field.Names is set, it's not an embedded type.
|
||||
if field.Names != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// A direct "sub-type" has an ast.Field as either:
|
||||
// Ident { "AppController" }
|
||||
// SelectorExpr { "rev", "Controller" }
|
||||
// Additionally, that can be wrapped by StarExprs.
|
||||
fieldType := field.Type
|
||||
pkgName, typeName := func() (string, string) {
|
||||
// Drill through any StarExprs.
|
||||
for {
|
||||
if starExpr, ok := fieldType.(*ast.StarExpr); ok {
|
||||
fieldType = starExpr.X
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// If the embedded type is in the same package, it's an Ident.
|
||||
if ident, ok := fieldType.(*ast.Ident); ok {
|
||||
return "", ident.Name
|
||||
}
|
||||
|
||||
if selectorExpr, ok := fieldType.(*ast.SelectorExpr); ok {
|
||||
if pkgIdent, ok := selectorExpr.X.(*ast.Ident); ok {
|
||||
return pkgIdent.Name, selectorExpr.Sel.Name
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}()
|
||||
|
||||
// If a typename wasn't found, skip it.
|
||||
if typeName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the import path for this type.
|
||||
// If it was referenced without a package name, use the current package import path.
|
||||
// Else, look up the package's import path by name.
|
||||
var importPath string
|
||||
if pkgName == "" {
|
||||
importPath = pkgImportPath
|
||||
} else {
|
||||
var ok bool
|
||||
if importPath, ok = imports[pkgName]; !ok {
|
||||
revel.RevelLog.Error("Failed to find import path for ", "package", pkgName, "type", typeName)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
controllerSpec.embeddedTypes = append(controllerSpec.embeddedTypes, &embeddedTypeName{
|
||||
ImportPath: importPath,
|
||||
StructName: typeName,
|
||||
})
|
||||
}
|
||||
|
||||
return append(specs, controllerSpec)
|
||||
}
|
||||
|
||||
// If decl is a Method declaration, it is summarized and added to the array
|
||||
// underneath its receiver type.
|
||||
// e.g. "Login" => {MethodSpec, MethodSpec, ..}
|
||||
func appendAction(fset *token.FileSet, mm methodMap, decl ast.Decl, pkgImportPath, pkgName string, imports map[string]string) {
|
||||
// Func declaration?
|
||||
funcDecl, ok := decl.(*ast.FuncDecl)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Have a receiver?
|
||||
if funcDecl.Recv == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Is it public?
|
||||
if !funcDecl.Name.IsExported() {
|
||||
return
|
||||
}
|
||||
|
||||
// Does it return a Result?
|
||||
if funcDecl.Type.Results == nil || len(funcDecl.Type.Results.List) != 1 {
|
||||
return
|
||||
}
|
||||
selExpr, ok := funcDecl.Type.Results.List[0].Type.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if selExpr.Sel.Name != "Result" {
|
||||
return
|
||||
}
|
||||
if pkgIdent, ok := selExpr.X.(*ast.Ident); !ok || imports[pkgIdent.Name] != revel.RevelImportPath {
|
||||
return
|
||||
}
|
||||
|
||||
method := &MethodSpec{
|
||||
Name: funcDecl.Name.Name,
|
||||
}
|
||||
|
||||
// Add a description of the arguments to the method.
|
||||
for _, field := range funcDecl.Type.Params.List {
|
||||
for _, name := range field.Names {
|
||||
var importPath string
|
||||
typeExpr := NewTypeExpr(pkgName, field.Type)
|
||||
if !typeExpr.Valid {
|
||||
revel.RevelLog.Warnf("Didn't understand argument '%s' of action %s. Ignoring.", name, getFuncName(funcDecl))
|
||||
return // We didn't understand one of the args. Ignore this action.
|
||||
}
|
||||
// Local object
|
||||
if typeExpr.PkgName == pkgName {
|
||||
importPath = pkgImportPath
|
||||
} else if typeExpr.PkgName != "" {
|
||||
var ok bool
|
||||
if importPath, ok = imports[typeExpr.PkgName]; !ok {
|
||||
revel.RevelLog.Errorf("Failed to find import for arg of type: %s , %s", typeExpr.PkgName, typeExpr.TypeName(""))
|
||||
}
|
||||
}
|
||||
method.Args = append(method.Args, &MethodArg{
|
||||
Name: name.Name,
|
||||
TypeExpr: typeExpr,
|
||||
ImportPath: importPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Add a description of the calls to Render from the method.
|
||||
// Inspect every node (e.g. always return true).
|
||||
method.RenderCalls = []*methodCall{}
|
||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||
// Is it a function call?
|
||||
callExpr, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Is it calling (*Controller).Render?
|
||||
selExpr, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// The type of the receiver is not easily available, so just store every
|
||||
// call to any method called Render.
|
||||
if selExpr.Sel.Name != "Render" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Add this call's args to the renderArgs.
|
||||
pos := fset.Position(callExpr.Lparen)
|
||||
methodCall := &methodCall{
|
||||
Line: pos.Line,
|
||||
Names: []string{},
|
||||
}
|
||||
for _, arg := range callExpr.Args {
|
||||
argIdent, ok := arg.(*ast.Ident)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
methodCall.Names = append(methodCall.Names, argIdent.Name)
|
||||
}
|
||||
method.RenderCalls = append(method.RenderCalls, methodCall)
|
||||
return true
|
||||
})
|
||||
|
||||
var recvTypeName string
|
||||
var recvType = funcDecl.Recv.List[0].Type
|
||||
if recvStarType, ok := recvType.(*ast.StarExpr); ok {
|
||||
recvTypeName = recvStarType.X.(*ast.Ident).Name
|
||||
} else {
|
||||
recvTypeName = recvType.(*ast.Ident).Name
|
||||
}
|
||||
|
||||
mm[recvTypeName] = append(mm[recvTypeName], method)
|
||||
}
|
||||
|
||||
// Scan app source code for calls to X.Y(), where X is of type *Validation.
|
||||
//
|
||||
// Recognize these scenarios:
|
||||
// - "Y" = "Validation" and is a member of the receiver.
|
||||
// (The common case for inline validation)
|
||||
// - "X" is passed in to the func as a parameter.
|
||||
// (For structs implementing Validated)
|
||||
//
|
||||
// The line number to which a validation call is attributed is that of the
|
||||
// surrounding ExprStmt. This is so that it matches what runtime.Callers()
|
||||
// reports.
|
||||
//
|
||||
// The end result is that we can set the default validation key for each call to
|
||||
// be the same as the local variable.
|
||||
func getValidationKeys(fset *token.FileSet, funcDecl *ast.FuncDecl, imports map[string]string) map[int]string {
|
||||
var (
|
||||
lineKeys = make(map[int]string)
|
||||
|
||||
// Check the func parameters and the receiver's members for the *revel.Validation type.
|
||||
validationParam = getValidationParameter(funcDecl, imports)
|
||||
)
|
||||
|
||||
ast.Inspect(funcDecl.Body, func(node ast.Node) bool {
|
||||
// e.g. c.Validation.Required(arg) or v.Required(arg)
|
||||
callExpr, ok := node.(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// e.g. c.Validation.Required or v.Required
|
||||
funcSelector, ok := callExpr.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
switch x := funcSelector.X.(type) {
|
||||
case *ast.SelectorExpr: // e.g. c.Validation
|
||||
if x.Sel.Name != "Validation" {
|
||||
return true
|
||||
}
|
||||
|
||||
case *ast.Ident: // e.g. v
|
||||
if validationParam == nil || x.Obj != validationParam {
|
||||
return true
|
||||
}
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
||||
if len(callExpr.Args) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Given the validation expression, extract the key.
|
||||
key := callExpr.Args[0]
|
||||
switch expr := key.(type) {
|
||||
case *ast.BinaryExpr:
|
||||
// If the argument is a binary expression, take the first expression.
|
||||
// (e.g. c.Validation.Required(myName != ""))
|
||||
key = expr.X
|
||||
case *ast.UnaryExpr:
|
||||
// If the argument is a unary expression, drill in.
|
||||
// (e.g. c.Validation.Required(!myBool)
|
||||
key = expr.X
|
||||
case *ast.BasicLit:
|
||||
// If it's a literal, skip it.
|
||||
return true
|
||||
}
|
||||
|
||||
if typeExpr := NewTypeExpr("", key); typeExpr.Valid {
|
||||
lineKeys[fset.Position(callExpr.End()).Line] = typeExpr.TypeName("")
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return lineKeys
|
||||
}
|
||||
|
||||
// Check to see if there is a *revel.Validation as an argument.
|
||||
func getValidationParameter(funcDecl *ast.FuncDecl, imports map[string]string) *ast.Object {
|
||||
for _, field := range funcDecl.Type.Params.List {
|
||||
starExpr, ok := field.Type.(*ast.StarExpr) // e.g. *revel.Validation
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
selExpr, ok := starExpr.X.(*ast.SelectorExpr) // e.g. revel.Validation
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
xIdent, ok := selExpr.X.(*ast.Ident) // e.g. rev
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if selExpr.Sel.Name == "Validation" && imports[xIdent.Name] == revel.RevelImportPath {
|
||||
return field.Names[0].Obj
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *TypeInfo) String() string {
|
||||
return s.ImportPath + "." + s.StructName
|
||||
}
|
||||
|
||||
func (s *embeddedTypeName) String() string {
|
||||
return s.ImportPath + "." + s.StructName
|
||||
}
|
||||
|
||||
// getStructTypeDecl checks if the given decl is a type declaration for a
|
||||
// struct. If so, the TypeSpec is returned.
|
||||
func getStructTypeDecl(decl ast.Decl, fset *token.FileSet) (spec *ast.TypeSpec, found bool) {
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if genDecl.Tok != token.TYPE {
|
||||
return
|
||||
}
|
||||
|
||||
if len(genDecl.Specs) == 0 {
|
||||
revel.RevelLog.Warnf("Surprising: %s:%d Decl contains no specifications", fset.Position(decl.Pos()).Filename, fset.Position(decl.Pos()).Line)
|
||||
return
|
||||
}
|
||||
|
||||
spec = genDecl.Specs[0].(*ast.TypeSpec)
|
||||
_, found = spec.Type.(*ast.StructType)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TypesThatEmbed returns all types that (directly or indirectly) embed the
|
||||
// target type, which must be a fully qualified type name,
|
||||
// e.g. "github.com/revel/revel.Controller"
|
||||
func (s *SourceInfo) TypesThatEmbed(targetType, packageFilter string) (filtered []*TypeInfo) {
|
||||
// Do a search in the "embedded type graph", starting with the target type.
|
||||
var (
|
||||
nodeQueue = []string{targetType}
|
||||
processed []string
|
||||
)
|
||||
for len(nodeQueue) > 0 {
|
||||
controllerSimpleName := nodeQueue[0]
|
||||
nodeQueue = nodeQueue[1:]
|
||||
processed = append(processed, controllerSimpleName)
|
||||
|
||||
// Look through all known structs.
|
||||
for _, spec := range s.StructSpecs {
|
||||
// If this one has been processed or is already in nodeQueue, then skip it.
|
||||
if revel.ContainsString(processed, spec.String()) ||
|
||||
revel.ContainsString(nodeQueue, spec.String()) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Look through the embedded types to see if the current type is among them.
|
||||
for _, embeddedType := range spec.embeddedTypes {
|
||||
|
||||
// If so, add this type's simple name to the nodeQueue, and its spec to
|
||||
// the filtered list.
|
||||
if controllerSimpleName == embeddedType.String() {
|
||||
nodeQueue = append(nodeQueue, spec.String())
|
||||
filtered = append(filtered, spec)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Strip out any specifications that contain a lower case
|
||||
for exit := false; !exit; exit = true {
|
||||
for i, filteredItem := range filtered {
|
||||
if unicode.IsLower([]rune(filteredItem.StructName)[0]) {
|
||||
revel.RevelLog.Debug("Skipping adding spec for unexported type",
|
||||
"type", filteredItem.StructName,
|
||||
"package", filteredItem.ImportPath)
|
||||
filtered = append(filtered[:i], filtered[i+1:]...)
|
||||
exit = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any missed types that where from expected packages
|
||||
for _, spec := range s.StructSpecs {
|
||||
if spec.PackageName == packageFilter {
|
||||
found := false
|
||||
for _, filteredItem := range filtered {
|
||||
if filteredItem.StructName == spec.StructName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
revel.RevelLog.Warn("Type found in package: "+packageFilter+
|
||||
", but did not embed from: "+filepath.Base(targetType),
|
||||
"name", spec.StructName, "path", spec.ImportPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ControllerSpecs returns the all the contollers that embeds
|
||||
// `revel.Controller`
|
||||
func (s *SourceInfo) ControllerSpecs() []*TypeInfo {
|
||||
if s.controllerSpecs == nil {
|
||||
s.controllerSpecs = s.TypesThatEmbed(revel.RevelImportPath+".Controller", "controllers")
|
||||
}
|
||||
return s.controllerSpecs
|
||||
}
|
||||
|
||||
// TestSuites returns the all the Application tests that embeds
|
||||
// `testing.TestSuite`
|
||||
func (s *SourceInfo) TestSuites() []*TypeInfo {
|
||||
if s.testSuites == nil {
|
||||
s.testSuites = s.TypesThatEmbed(revel.RevelImportPath+"/testing.TestSuite", "testsuite")
|
||||
}
|
||||
return s.testSuites
|
||||
}
|
||||
|
||||
// TypeExpr provides a type name that may be rewritten to use a package name.
|
||||
type TypeExpr struct {
|
||||
Expr string // The unqualified type expression, e.g. "[]*MyType"
|
||||
PkgName string // The default package idenifier
|
||||
pkgIndex int // The index where the package identifier should be inserted.
|
||||
Valid bool
|
||||
}
|
||||
|
||||
// TypeName returns the fully-qualified type name for this expression.
|
||||
// The caller may optionally specify a package name to override the default.
|
||||
func (e TypeExpr) TypeName(pkgOverride string) string {
|
||||
pkgName := revel.FirstNonEmpty(pkgOverride, e.PkgName)
|
||||
if pkgName == "" {
|
||||
return e.Expr
|
||||
}
|
||||
return e.Expr[:e.pkgIndex] + pkgName + "." + e.Expr[e.pkgIndex:]
|
||||
}
|
||||
|
||||
// NewTypeExpr returns the syntactic expression for referencing this type in Go.
|
||||
func NewTypeExpr(pkgName string, expr ast.Expr) TypeExpr {
|
||||
switch t := expr.(type) {
|
||||
case *ast.Ident:
|
||||
if IsBuiltinType(t.Name) {
|
||||
pkgName = ""
|
||||
}
|
||||
return TypeExpr{t.Name, pkgName, 0, true}
|
||||
case *ast.SelectorExpr:
|
||||
e := NewTypeExpr(pkgName, t.X)
|
||||
return TypeExpr{t.Sel.Name, e.Expr, 0, e.Valid}
|
||||
case *ast.StarExpr:
|
||||
e := NewTypeExpr(pkgName, t.X)
|
||||
return TypeExpr{"*" + e.Expr, e.PkgName, e.pkgIndex + 1, e.Valid}
|
||||
case *ast.ArrayType:
|
||||
e := NewTypeExpr(pkgName, t.Elt)
|
||||
return TypeExpr{"[]" + e.Expr, e.PkgName, e.pkgIndex + 2, e.Valid}
|
||||
case *ast.Ellipsis:
|
||||
e := NewTypeExpr(pkgName, t.Elt)
|
||||
return TypeExpr{"[]" + e.Expr, e.PkgName, e.pkgIndex + 2, e.Valid}
|
||||
default:
|
||||
revel.RevelLog.Error("Failed to generate name for field. Make sure the field name is valid.")
|
||||
}
|
||||
return TypeExpr{Valid: false}
|
||||
}
|
||||
|
||||
var builtInTypes = map[string]struct{}{
|
||||
"bool": {},
|
||||
"byte": {},
|
||||
"complex128": {},
|
||||
"complex64": {},
|
||||
"error": {},
|
||||
"float32": {},
|
||||
"float64": {},
|
||||
"int": {},
|
||||
"int16": {},
|
||||
"int32": {},
|
||||
"int64": {},
|
||||
"int8": {},
|
||||
"rune": {},
|
||||
"string": {},
|
||||
"uint": {},
|
||||
"uint16": {},
|
||||
"uint32": {},
|
||||
"uint64": {},
|
||||
"uint8": {},
|
||||
"uintptr": {},
|
||||
}
|
||||
|
||||
// IsBuiltinType checks the given type is built-in types of Go
|
||||
func IsBuiltinType(name string) bool {
|
||||
_, ok := builtInTypes[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func importPathFromPath(root string) string {
|
||||
vendoringPath := revel.BasePath + "/vendor/"
|
||||
if strings.HasPrefix(root, vendoringPath) {
|
||||
return filepath.ToSlash(root[len(vendoringPath):])
|
||||
}
|
||||
for _, gopath := range filepath.SplitList(build.Default.GOPATH) {
|
||||
srcPath := filepath.Join(gopath, "src")
|
||||
if strings.HasPrefix(root, srcPath) {
|
||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||
}
|
||||
}
|
||||
|
||||
srcPath := filepath.Join(build.Default.GOROOT, "src", "pkg")
|
||||
if strings.HasPrefix(root, srcPath) {
|
||||
revel.RevelLog.Warn("Code path should be in GOPATH, but is in GOROOT:", "path", root)
|
||||
return filepath.ToSlash(root[len(srcPath)+1:])
|
||||
}
|
||||
|
||||
revel.RevelLog.Error("Unexpected! Code path is not in GOPATH:", "path", root)
|
||||
return ""
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package harness
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
const validationKeysSource = `
|
||||
package test
|
||||
|
||||
func (c *Application) testFunc(a, b int, user models.User) revel.Result {
|
||||
// Line 5
|
||||
c.Validation.Required(a)
|
||||
c.Validation.Required(a).Message("Error message")
|
||||
c.Validation.Required(a).
|
||||
Message("Error message")
|
||||
|
||||
// Line 11
|
||||
c.Validation.Required(user.Name)
|
||||
c.Validation.Required(user.Name).Message("Error message")
|
||||
|
||||
// Line 15
|
||||
c.Validation.MinSize(b, 12)
|
||||
c.Validation.MinSize(b, 12).Message("Error message")
|
||||
c.Validation.MinSize(b,
|
||||
12)
|
||||
|
||||
// Line 21
|
||||
c.Validation.Required(b == 5)
|
||||
}
|
||||
|
||||
func (m Model) Validate(v *revel.Validation) {
|
||||
// Line 26
|
||||
v.Required(m.name)
|
||||
v.Required(m.name == "something").
|
||||
Message("Error Message")
|
||||
v.Required(!m.bool)
|
||||
}
|
||||
`
|
||||
|
||||
var expectedValidationKeys = []map[int]string{
|
||||
{
|
||||
6: "a",
|
||||
7: "a",
|
||||
8: "a",
|
||||
12: "user.Name",
|
||||
13: "user.Name",
|
||||
16: "b",
|
||||
17: "b",
|
||||
19: "b",
|
||||
22: "b",
|
||||
}, {
|
||||
27: "m.name",
|
||||
28: "m.name",
|
||||
30: "m.bool",
|
||||
},
|
||||
}
|
||||
|
||||
// This tests the recording of line number to validation key of the preceeding
|
||||
// example source.
|
||||
func TestGetValidationKeys(t *testing.T) {
|
||||
fset := token.NewFileSet()
|
||||
|
||||
file, err := parser.ParseFile(fset, "validationKeysSource", validationKeysSource, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(file.Decls) != 2 {
|
||||
t.Fatal("Expected 2 decl in the source, found", len(file.Decls))
|
||||
}
|
||||
|
||||
for i, decl := range file.Decls {
|
||||
lineKeys := getValidationKeys(fset, decl.(*ast.FuncDecl), map[string]string{"revel": revel.RevelImportPath})
|
||||
for k, v := range expectedValidationKeys[i] {
|
||||
if lineKeys[k] != v {
|
||||
t.Errorf("Not found - %d: %v - Actual Map: %v", k, v, lineKeys)
|
||||
}
|
||||
}
|
||||
|
||||
if len(lineKeys) != len(expectedValidationKeys[i]) {
|
||||
t.Error("Validation key map not the same size as expected:", lineKeys)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var TypeExprs = map[string]TypeExpr{
|
||||
"int": {"int", "", 0, true},
|
||||
"*int": {"*int", "", 1, true},
|
||||
"[]int": {"[]int", "", 2, true},
|
||||
"...int": {"[]int", "", 2, true},
|
||||
"[]*int": {"[]*int", "", 3, true},
|
||||
"...*int": {"[]*int", "", 3, true},
|
||||
"MyType": {"MyType", "pkg", 0, true},
|
||||
"*MyType": {"*MyType", "pkg", 1, true},
|
||||
"[]MyType": {"[]MyType", "pkg", 2, true},
|
||||
"...MyType": {"[]MyType", "pkg", 2, true},
|
||||
"[]*MyType": {"[]*MyType", "pkg", 3, true},
|
||||
"...*MyType": {"[]*MyType", "pkg", 3, true},
|
||||
}
|
||||
|
||||
func TestTypeExpr(t *testing.T) {
|
||||
for typeStr, expected := range TypeExprs {
|
||||
// Handle arrays and ... myself, since ParseExpr() does not.
|
||||
array := strings.HasPrefix(typeStr, "[]")
|
||||
if array {
|
||||
typeStr = typeStr[2:]
|
||||
}
|
||||
|
||||
ellipsis := strings.HasPrefix(typeStr, "...")
|
||||
if ellipsis {
|
||||
typeStr = typeStr[3:]
|
||||
}
|
||||
|
||||
expr, err := parser.ParseExpr(typeStr)
|
||||
if err != nil {
|
||||
t.Error("Failed to parse test expr:", typeStr)
|
||||
continue
|
||||
}
|
||||
|
||||
if array {
|
||||
expr = &ast.ArrayType{Lbrack: expr.Pos(), Len: nil, Elt: expr}
|
||||
}
|
||||
if ellipsis {
|
||||
expr = &ast.Ellipsis{Ellipsis: expr.Pos(), Elt: expr}
|
||||
}
|
||||
|
||||
actual := NewTypeExpr("pkg", expr)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Error("Fail, expected", expected, ", was", actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessBookingSource(t *testing.T) {
|
||||
revel.Init("prod", "github.com/revel/examples/booking", "")
|
||||
sourceInfo, err := ProcessSource([]string{revel.AppPath})
|
||||
if err != nil {
|
||||
t.Fatal("Failed to process booking source with error:", err)
|
||||
}
|
||||
|
||||
controllerPackage := "github.com/revel/examples/booking/app/controllers"
|
||||
expectedControllerSpecs := []*TypeInfo{
|
||||
{"GorpController", controllerPackage, "controllers", nil, nil},
|
||||
{"Application", controllerPackage, "controllers", nil, nil},
|
||||
{"Hotels", controllerPackage, "controllers", nil, nil},
|
||||
}
|
||||
if len(sourceInfo.ControllerSpecs()) != len(expectedControllerSpecs) {
|
||||
t.Errorf("Unexpected number of controllers found. Expected %d, Found %d",
|
||||
len(expectedControllerSpecs), len(sourceInfo.ControllerSpecs()))
|
||||
}
|
||||
|
||||
NEXT_TEST:
|
||||
for _, expected := range expectedControllerSpecs {
|
||||
for _, actual := range sourceInfo.ControllerSpecs() {
|
||||
if actual.StructName == expected.StructName {
|
||||
if actual.ImportPath != expected.ImportPath {
|
||||
t.Errorf("%s expected to have import path %s, actual %s",
|
||||
actual.StructName, expected.ImportPath, actual.ImportPath)
|
||||
}
|
||||
if actual.PackageName != expected.PackageName {
|
||||
t.Errorf("%s expected to have package name %s, actual %s",
|
||||
actual.StructName, expected.PackageName, actual.PackageName)
|
||||
}
|
||||
continue NEXT_TEST
|
||||
}
|
||||
}
|
||||
t.Errorf("Expected to find controller %s, but did not. Actuals: %s",
|
||||
expected.StructName, sourceInfo.ControllerSpecs())
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkProcessBookingSource(b *testing.B) {
|
||||
revel.Init("", "github.com/revel/examples/booking", "")
|
||||
revel.GetRootLogHandler().Disable()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := ProcessSource(revel.CodePaths)
|
||||
if err != nil {
|
||||
b.Error("Unexpected error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/leanote/leanote/cmd/harness"
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var cmdBuild = &Command{
|
||||
UsageLine: "build [import path] [target path] [run mode]",
|
||||
Short: "build a Revel application (e.g. for deployment)",
|
||||
Long: `
|
||||
Build the Revel web application named by the given import path.
|
||||
This allows it to be deployed and run on a machine that lacks a Go installation.
|
||||
|
||||
The run mode is used to select which set of app.conf configuration should
|
||||
apply and may be used to determine logic in the application itself.
|
||||
|
||||
Run mode defaults to "dev".
|
||||
|
||||
WARNING: The target path will be completely deleted, if it already exists!
|
||||
|
||||
For example:
|
||||
|
||||
revel build github.com/revel/examples/chat /tmp/chat
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdBuild.Run = buildApp
|
||||
}
|
||||
|
||||
func buildApp(args []string) {
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "%s\n%s", cmdBuild.UsageLine, cmdBuild.Long)
|
||||
return
|
||||
}
|
||||
|
||||
appImportPath, destPath, mode := args[0], args[1], DefaultRunMode
|
||||
if len(args) >= 3 {
|
||||
mode = args[2]
|
||||
}
|
||||
|
||||
if !revel.Initialized {
|
||||
revel.Init(mode, appImportPath, "")
|
||||
}
|
||||
|
||||
// First, verify that it is either already empty or looks like a previous
|
||||
// build (to avoid clobbering anything)
|
||||
if exists(destPath) && !empty(destPath) && !exists(filepath.Join(destPath, "run.sh")) {
|
||||
errorf("Abort: %s exists and does not look like a build directory.", destPath)
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(destPath); err != nil && !os.IsNotExist(err) {
|
||||
revel.RevelLog.Fatal("Remove all error","error", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(destPath, 0777); err != nil {
|
||||
revel.RevelLog.Fatal("makedir error","error",err)
|
||||
}
|
||||
|
||||
app, reverr := harness.Build()
|
||||
panicOnError(reverr, "Failed to build")
|
||||
|
||||
// Included are:
|
||||
// - run scripts
|
||||
// - binary
|
||||
// - revel
|
||||
// - app
|
||||
|
||||
// Revel and the app are in a directory structure mirroring import path
|
||||
srcPath := filepath.Join(destPath, "src")
|
||||
destBinaryPath := filepath.Join(destPath, filepath.Base(app.BinaryPath))
|
||||
tmpRevelPath := filepath.Join(srcPath, filepath.FromSlash(revel.RevelImportPath))
|
||||
mustCopyFile(destBinaryPath, app.BinaryPath)
|
||||
mustChmod(destBinaryPath, 0755)
|
||||
_ = mustCopyDir(filepath.Join(tmpRevelPath, "conf"), filepath.Join(revel.RevelPath, "conf"), nil)
|
||||
_ = mustCopyDir(filepath.Join(tmpRevelPath, "templates"), filepath.Join(revel.RevelPath, "templates"), nil)
|
||||
_ = mustCopyDir(filepath.Join(srcPath, filepath.FromSlash(appImportPath)), revel.BasePath, nil)
|
||||
|
||||
// Find all the modules used and copy them over.
|
||||
config := revel.Config.Raw()
|
||||
modulePaths := make(map[string]string) // import path => filesystem path
|
||||
for _, section := range config.Sections() {
|
||||
options, _ := config.SectionOptions(section)
|
||||
for _, key := range options {
|
||||
if !strings.HasPrefix(key, "module.") {
|
||||
continue
|
||||
}
|
||||
moduleImportPath, _ := config.String(section, key)
|
||||
if moduleImportPath == "" {
|
||||
continue
|
||||
}
|
||||
modulePath, err := revel.ResolveImportPath(moduleImportPath)
|
||||
if err != nil {
|
||||
revel.RevelLog.Fatalf("Failed to load module %s: %s", key[len("module."):], err)
|
||||
}
|
||||
modulePaths[moduleImportPath] = modulePath
|
||||
}
|
||||
}
|
||||
for importPath, fsPath := range modulePaths {
|
||||
_ = mustCopyDir(filepath.Join(srcPath, importPath), fsPath, nil)
|
||||
}
|
||||
|
||||
tmplData, runShPath := map[string]interface{}{
|
||||
"BinName": filepath.Base(app.BinaryPath),
|
||||
"ImportPath": appImportPath,
|
||||
"Mode": mode,
|
||||
}, filepath.Join(destPath, "run.sh")
|
||||
|
||||
mustRenderTemplate(
|
||||
runShPath,
|
||||
filepath.Join(revel.RevelPath, "..", "cmd", "revel", "package_run.sh.template"),
|
||||
tmplData)
|
||||
|
||||
mustChmod(runShPath, 0755)
|
||||
|
||||
mustRenderTemplate(
|
||||
filepath.Join(destPath, "run.bat"),
|
||||
filepath.Join(revel.RevelPath, "..", "cmd", "revel", "package_run.bat.template"),
|
||||
tmplData)
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var cmdClean = &Command{
|
||||
UsageLine: "clean [import path]",
|
||||
Short: "clean a Revel application's temp files",
|
||||
Long: `
|
||||
Clean the Revel web application named by the given import path.
|
||||
|
||||
For example:
|
||||
|
||||
revel clean github.com/revel/examples/chat
|
||||
|
||||
It removes the app/tmp and app/routes directory.
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdClean.Run = cleanApp
|
||||
}
|
||||
|
||||
func cleanApp(args []string) {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprintf(os.Stderr, cmdClean.Long)
|
||||
return
|
||||
}
|
||||
|
||||
appPkg, err := build.Import(args[0], "", build.FindOnly)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Abort: Failed to find import path:", err)
|
||||
return
|
||||
}
|
||||
|
||||
purgeDirs := []string{
|
||||
filepath.Join(appPkg.Dir, "app", "tmp"),
|
||||
filepath.Join(appPkg.Dir, "app", "routes"),
|
||||
}
|
||||
|
||||
for _, dir := range purgeDirs {
|
||||
fmt.Println("Removing:", dir)
|
||||
err = os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Abort:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/build"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var cmdNew = &Command{
|
||||
UsageLine: "new [path] [skeleton]",
|
||||
Short: "create a skeleton Revel application",
|
||||
Long: `
|
||||
New creates a few files to get a new Revel application running quickly.
|
||||
|
||||
It puts all of the files in the given import path, taking the final element in
|
||||
the path to be the app name.
|
||||
|
||||
Skeleton is an optional argument, provided as an import path
|
||||
|
||||
For example:
|
||||
|
||||
revel new import/path/helloworld
|
||||
|
||||
revel new import/path/helloworld import/path/skeleton
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdNew.Run = newApp
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
// go related paths
|
||||
gopath string
|
||||
gocmd string
|
||||
srcRoot string
|
||||
|
||||
// revel related paths
|
||||
revelPkg *build.Package
|
||||
revelCmdPkg *build.Package
|
||||
appPath string
|
||||
appName string
|
||||
basePath string
|
||||
importPath string
|
||||
skeletonPath string
|
||||
)
|
||||
|
||||
func newApp(args []string) {
|
||||
// check for proper args by count
|
||||
if len(args) == 0 {
|
||||
errorf("No import path given.\nRun 'revel help new' for usage.\n")
|
||||
}
|
||||
if len(args) > 2 {
|
||||
errorf("Too many arguments provided.\nRun 'revel help new' for usage.\n")
|
||||
}
|
||||
|
||||
// checking and setting go paths
|
||||
initGoPaths()
|
||||
|
||||
// checking and setting application
|
||||
setApplicationPath(args)
|
||||
|
||||
// checking and setting skeleton
|
||||
setSkeletonPath(args)
|
||||
|
||||
// copy files to new app directory
|
||||
copyNewAppFiles()
|
||||
|
||||
// goodbye world
|
||||
fmt.Fprintln(os.Stdout, "Your application is ready:\n ", appPath)
|
||||
fmt.Fprintln(os.Stdout, "\nYou can run it with:\n revel run", importPath)
|
||||
}
|
||||
|
||||
const alphaNumeric = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
func generateSecret() string {
|
||||
chars := make([]byte, 64)
|
||||
for i := 0; i < 64; i++ {
|
||||
chars[i] = alphaNumeric[rand.Intn(len(alphaNumeric))]
|
||||
}
|
||||
return string(chars)
|
||||
}
|
||||
|
||||
// lookup and set Go related variables
|
||||
func initGoPaths() {
|
||||
// lookup go path
|
||||
gopath = build.Default.GOPATH
|
||||
if gopath == "" {
|
||||
errorf("Abort: GOPATH environment variable is not set. " +
|
||||
"Please refer to http://golang.org/doc/code.html to configure your Go environment.")
|
||||
}
|
||||
|
||||
// check for go executable
|
||||
var err error
|
||||
gocmd, err = exec.LookPath("go")
|
||||
if err != nil {
|
||||
errorf("Go executable not found in PATH.")
|
||||
}
|
||||
|
||||
// revel/revel#1004 choose go path relative to current working directory
|
||||
workingDir, _ := os.Getwd()
|
||||
goPathList := filepath.SplitList(gopath)
|
||||
for _, path := range goPathList {
|
||||
if strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
||||
srcRoot = path
|
||||
break
|
||||
}
|
||||
|
||||
path, _ = filepath.EvalSymlinks(path)
|
||||
if len(path) > 0 && strings.HasPrefix(strings.ToLower(workingDir), strings.ToLower(path)) {
|
||||
srcRoot = path
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(srcRoot) == 0 {
|
||||
revel.RevelLog.Fatal("Abort: could not create a Revel application outside of GOPATH.")
|
||||
}
|
||||
|
||||
// set go src path
|
||||
srcRoot = filepath.Join(srcRoot, "src")
|
||||
}
|
||||
|
||||
func setApplicationPath(args []string) {
|
||||
var err error
|
||||
importPath = args[0]
|
||||
|
||||
// revel/revel#1014 validate relative path, we cannot use built-in functions
|
||||
// since Go import path is valid relative path too.
|
||||
// so check basic part of the path, which is "."
|
||||
if filepath.IsAbs(importPath) || strings.HasPrefix(importPath, ".") {
|
||||
errorf("Abort: '%s' looks like a directory. Please provide a Go import path instead.",
|
||||
importPath)
|
||||
}
|
||||
|
||||
appPath = filepath.Join(srcRoot, filepath.FromSlash(importPath))
|
||||
|
||||
_, err = build.Import(importPath, "", build.FindOnly)
|
||||
if err == nil && !empty(appPath) {
|
||||
errorf("Abort: Import path %s already exists.\n", importPath)
|
||||
}
|
||||
|
||||
revelPkg, err = build.Import(revel.RevelImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
errorf("Abort: Could not find Revel source code: %s\n", err)
|
||||
}
|
||||
|
||||
appName = filepath.Base(appPath)
|
||||
basePath = filepath.ToSlash(filepath.Dir(importPath))
|
||||
|
||||
if basePath == "." {
|
||||
// we need to remove the a single '.' when
|
||||
// the app is in the $GOROOT/src directory
|
||||
basePath = ""
|
||||
} else {
|
||||
// we need to append a '/' when the app is
|
||||
// is a subdirectory such as $GOROOT/src/path/to/revelapp
|
||||
basePath += "/"
|
||||
}
|
||||
}
|
||||
|
||||
func setSkeletonPath(args []string) {
|
||||
var err error
|
||||
if len(args) == 2 { // user specified
|
||||
skeletonName := args[1]
|
||||
_, err = build.Import(skeletonName, "", build.FindOnly)
|
||||
if err != nil {
|
||||
// Execute "go get <pkg>"
|
||||
getCmd := exec.Command(gocmd, "get", "-d", skeletonName)
|
||||
fmt.Println("Exec:", getCmd.Args)
|
||||
getOutput, err := getCmd.CombinedOutput()
|
||||
|
||||
// check getOutput for no buildible string
|
||||
bpos := bytes.Index(getOutput, []byte("no buildable Go source files in"))
|
||||
if err != nil && bpos == -1 {
|
||||
errorf("Abort: Could not find or 'go get' Skeleton source code: %s\n%s\n", getOutput, skeletonName)
|
||||
}
|
||||
}
|
||||
// use the
|
||||
skeletonPath = filepath.Join(srcRoot, skeletonName)
|
||||
|
||||
} else {
|
||||
// use the revel default
|
||||
revelCmdPkg, err = build.Import(RevelCmdImportPath, "", build.FindOnly)
|
||||
if err != nil {
|
||||
errorf("Abort: Could not find Revel Cmd source code: %s\n", err)
|
||||
}
|
||||
|
||||
skeletonPath = filepath.Join(revelCmdPkg.Dir, "revel", "skeleton")
|
||||
}
|
||||
}
|
||||
|
||||
func copyNewAppFiles() {
|
||||
var err error
|
||||
err = os.MkdirAll(appPath, 0777)
|
||||
panicOnError(err, "Failed to create directory "+appPath)
|
||||
|
||||
_ = mustCopyDir(appPath, skeletonPath, map[string]interface{}{
|
||||
// app.conf
|
||||
"AppName": appName,
|
||||
"BasePath": basePath,
|
||||
"Secret": generateSecret(),
|
||||
})
|
||||
|
||||
// Dotfiles are skipped by mustCopyDir, so we have to explicitly copy the .gitignore.
|
||||
gitignore := ".gitignore"
|
||||
mustCopyFile(filepath.Join(appPath, gitignore), filepath.Join(skeletonPath, gitignore))
|
||||
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var cmdPackage = &Command{
|
||||
UsageLine: "package [import path] [run mode]",
|
||||
Short: "package a Revel application (e.g. for deployment)",
|
||||
Long: `
|
||||
Package the Revel web application named by the given import path.
|
||||
This allows it to be deployed and run on a machine that lacks a Go installation.
|
||||
|
||||
The run mode is used to select which set of app.conf configuration should
|
||||
apply and may be used to determine logic in the application itself.
|
||||
|
||||
Run mode defaults to "dev".
|
||||
|
||||
For example:
|
||||
|
||||
revel package github.com/revel/examples/chat
|
||||
`,
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdPackage.Run = packageApp
|
||||
}
|
||||
|
||||
func packageApp(args []string) {
|
||||
if len(args) == 0 {
|
||||
fmt.Fprint(os.Stderr, cmdPackage.Long)
|
||||
return
|
||||
}
|
||||
|
||||
// Determine the run mode.
|
||||
mode := DefaultRunMode
|
||||
if len(args) >= 2 {
|
||||
mode = args[1]
|
||||
}
|
||||
|
||||
appImportPath := args[0]
|
||||
revel.Init(mode, appImportPath, "")
|
||||
|
||||
// Remove the archive if it already exists.
|
||||
destFile := filepath.Base(revel.BasePath) + ".tar.gz"
|
||||
if err := os.Remove(destFile); err != nil && !os.IsNotExist(err) {
|
||||
revel.RevelLog.Fatal("Unable to remove target file","error",err,"file",destFile)
|
||||
}
|
||||
|
||||
// Collect stuff in a temp directory.
|
||||
tmpDir, err := ioutil.TempDir("", filepath.Base(revel.BasePath))
|
||||
panicOnError(err, "Failed to get temp dir")
|
||||
|
||||
buildApp([]string{args[0], tmpDir, mode})
|
||||
|
||||
// Create the zip file.
|
||||
archiveName := mustTarGzDir(destFile, tmpDir)
|
||||
|
||||
fmt.Println("Your archive is ready:", archiveName)
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
@echo off
|
||||
{{.BinName}} -importPath {{.ImportPath}} -srcPath %CD%\src -runMode {{.Mode}}
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
SCRIPTPATH=$(cd "$(dirname "$0")"; pwd)
|
||||
"$SCRIPTPATH/{{.BinName}}" -importPath {{.ImportPath}} -srcPath "$SCRIPTPATH/src" -runMode {{.Mode}}
|
||||
@@ -1,144 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The command line tool for running Revel apps.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/agtorre/gocolorize"
|
||||
)
|
||||
|
||||
const (
|
||||
// RevelCmdImportPath Revel framework cmd tool import path
|
||||
RevelCmdImportPath = "github.com/revel/cmd"
|
||||
|
||||
// DefaultRunMode for revel's application
|
||||
DefaultRunMode = "dev"
|
||||
)
|
||||
|
||||
// Command structure cribbed from the genius organization of the "go" command.
|
||||
type Command struct {
|
||||
Run func(args []string)
|
||||
UsageLine, Short, Long string
|
||||
}
|
||||
|
||||
// Name returns command name from usage line
|
||||
func (cmd *Command) Name() string {
|
||||
name := cmd.UsageLine
|
||||
i := strings.Index(name, " ")
|
||||
if i >= 0 {
|
||||
name = name[:i]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
var commands = []*Command{
|
||||
cmdNew,
|
||||
cmdRun,
|
||||
cmdBuild,
|
||||
cmdPackage,
|
||||
cmdClean,
|
||||
cmdTest,
|
||||
cmdVersion,
|
||||
}
|
||||
|
||||
func main() {
|
||||
if runtime.GOOS == "windows" {
|
||||
gocolorize.SetPlain(true)
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, gocolorize.NewColor("blue").Paint(header))
|
||||
flag.Usage = func() { usage(1) }
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
|
||||
if len(args) < 1 || args[0] == "help" {
|
||||
if len(args) == 1 {
|
||||
usage(0)
|
||||
}
|
||||
if len(args) > 1 {
|
||||
for _, cmd := range commands {
|
||||
if cmd.Name() == args[1] {
|
||||
tmpl(os.Stdout, helpTemplate, cmd)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
usage(2)
|
||||
}
|
||||
|
||||
// Commands use panic to abort execution when something goes wrong.
|
||||
// Panics are logged at the point of error. Ignore those.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if _, ok := err.(LoggedError); !ok {
|
||||
// This panic was not expected / logged.
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, cmd := range commands {
|
||||
if cmd.Name() == args[0] {
|
||||
cmd.Run(args[1:])
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
errorf("unknown command %q\nRun 'revel help' for usage.\n", args[0])
|
||||
}
|
||||
|
||||
func errorf(format string, args ...interface{}) {
|
||||
// Ensure the user's command prompt starts on the next line.
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format += "\n"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, format, args...)
|
||||
panic(LoggedError{}) // Panic instead of os.Exit so that deferred will run.
|
||||
}
|
||||
|
||||
const header = `~
|
||||
~ revel! http://revel.github.io
|
||||
~
|
||||
`
|
||||
|
||||
const usageTemplate = `usage: revel command [arguments]
|
||||
|
||||
The commands are:
|
||||
{{range .}}
|
||||
{{.Name | printf "%-11s"}} {{.Short}}{{end}}
|
||||
|
||||
Use "revel help [command]" for more information.
|
||||
`
|
||||
|
||||
var helpTemplate = `usage: revel {{.UsageLine}}
|
||||
{{.Long}}
|
||||
`
|
||||
|
||||
func usage(exitCode int) {
|
||||
tmpl(os.Stderr, usageTemplate, commands)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
|
||||
func tmpl(w io.Writer, text string, data interface{}) {
|
||||
t := template.New("top")
|
||||
template.Must(t.Parse(text))
|
||||
if err := t.Execute(w, data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
|
||||
// Revel Framework source code and usage is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"strconv"
|
||||
|
||||
"github.com/leanote/leanote/cmd/harness"
|
||||
"github.com/revel/revel"
|
||||
)
|
||||
|
||||
var cmdRun = &Command{
|
||||
UsageLine: "run [import path] [run mode] [port]",
|
||||
Short: "run a Revel application",
|
||||
Long: `
|
||||
Run the Revel web application named by the given import path.
|
||||
|
||||
For example, to run the chat room sample application:
|
||||
|
||||
revel run github.com/revel/examples/chat dev
|
||||
|
||||
The run mode is used to select which set of app.conf configuration should
|
||||
apply and may be used to determine logic in the application itself.
|
||||
|
||||
Run mode defaults to "dev".
|
||||
|
||||
You can set a port as an optional third parameter. For example:
|
||||
|
||||
revel run github.com/revel/examples/chat prod 8080`,
|
||||
}
|
||||
|
||||
// RunArgs holds revel run parameters
|
||||
type RunArgs struct {
|
||||
ImportPath string
|
||||
Mode string
|
||||
Port int
|
||||
}
|
||||
|
||||
func init() {
|
||||
cmdRun.Run = runApp
|
||||
}
|
||||
|
||||
func parseRunArgs(args []string) *RunArgs {
|
||||
inputArgs := RunArgs{
|
||||
ImportPath: importPathFromCurrentDir(),
|
||||
Mode: DefaultRunMode,
|
||||
Port: revel.HTTPPort,
|
||||
}
|
||||
switch len(args) {
|
||||
case 3:
|
||||
// Possible combinations
|
||||
// revel run [import-path] [run-mode] [port]
|
||||
port, err := strconv.Atoi(args[2])
|
||||
if err != nil {
|
||||
errorf("Failed to parse port as integer: %s", args[2])
|
||||
}
|
||||
inputArgs.ImportPath = args[0]
|
||||
inputArgs.Mode = args[1]
|
||||
inputArgs.Port = port
|
||||
case 2:
|
||||
// Possible combinations
|
||||
// 1. revel run [import-path] [run-mode]
|
||||
// 2. revel run [import-path] [port]
|
||||
// 3. revel run [run-mode] [port]
|
||||
if _, err := build.Import(args[0], "", build.FindOnly); err == nil {
|
||||
// 1st arg is the import path
|
||||
inputArgs.ImportPath = args[0]
|
||||
if port, err := strconv.Atoi(args[1]); err == nil {
|
||||
// 2nd arg is the port number
|
||||
inputArgs.Port = port
|
||||
} else {
|
||||
// 2nd arg is the run mode
|
||||
inputArgs.Mode = args[1]
|
||||
}
|
||||
} else {
|
||||
// 1st arg is the run mode
|
||||
port, err := strconv.Atoi(args[1])
|
||||
if err != nil {
|
||||
errorf("Failed to parse port as integer: %s", args[1])
|
||||
}
|
||||
inputArgs.Mode = args[0]
|
||||
inputArgs.Port = port
|
||||
}
|
||||
case 1:
|
||||
// Possible combinations
|
||||
// 1. revel run [import-path]
|
||||
// 2. revel run [port]
|
||||
// 3. revel run [run-mode]
|
||||
_, err := build.Import(args[0], "", build.FindOnly)
|
||||
if err != nil {
|
||||
revel.RevelLog.Warn("Unable to run using an import path, assuming import path is working directory %s %s", "Argument", args[0], "error", err.Error())
|
||||
}
|
||||
println("Trying to build with", args[0], err)
|
||||
if err == nil {
|
||||
// 1st arg is the import path
|
||||
inputArgs.ImportPath = args[0]
|
||||
} else if port, err := strconv.Atoi(args[0]); err == nil {
|
||||
// 1st arg is the port number
|
||||
inputArgs.Port = port
|
||||
} else {
|
||||
// 1st arg is the run mode
|
||||
inputArgs.Mode = args[0]
|
||||
}
|
||||
}
|
||||
|
||||
return &inputArgs
|
||||
}
|
||||
|
||||
func runApp(args []string) {
|
||||
runArgs := parseRunArgs(args)
|
||||
|
||||
// Find and parse app.conf
|
||||
revel.Init(runArgs.Mode, runArgs.ImportPath, "")
|
||||
revel.LoadMimeConfig()
|
||||
|
||||
// fallback to default port
|
||||
if runArgs.Port == 0 {
|
||||
runArgs.Port = revel.HTTPPort
|
||||
}
|
||||
|
||||
revel.RevelLog.Infof("Running %s (%s) in %s mode\n", revel.AppName, revel.ImportPath, runArgs.Mode)
|
||||
revel.RevelLog.Debug("Base path:", "path", revel.BasePath)
|
||||
|
||||
// If the app is run in "watched" mode, use the harness to run it.
|
||||
if revel.Config.BoolDefault("watch", true) && revel.Config.BoolDefault("watch.code", true) {
|
||||
revel.RevelLog.Debug("Running in watched mode.")
|
||||
revel.HTTPPort = runArgs.Port
|
||||
harness.NewHarness().Run() // Never returns.
|
||||
}
|
||||
|
||||
// Else, just build and run the app.
|
||||
revel.RevelLog.Debug("Running in live build mode.")
|
||||
app, err := harness.Build()
|
||||
if err != nil {
|
||||
errorf("Failed to build app: %s", err)
|
||||
}
|
||||
app.Port = runArgs.Port
|
||||
app.Cmd().Run()
|
||||
}
|
||||
3
cmd/leanote_revel/skeleton/.gitignore
vendored
3
cmd/leanote_revel/skeleton/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
test-results/
|
||||
tmp/
|
||||
routes/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user