mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-02-03 23:40:14 +08:00
commit
6234b951b5
@ -1,18 +1,20 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Solve a charity's problem with the Julia programming language)
|
||||
[#]: via: (https://opensource.com/article/21/1/solve-problem-julia)
|
||||
[#]: author: (Chris Hermansen https://opensource.com/users/clhermansen)
|
||||
[#]: subject: "Solve a charity's problem with the Julia programming language"
|
||||
[#]: via: "https://opensource.com/article/21/1/solve-problem-julia"
|
||||
[#]: author: "Chris Hermansen https://opensource.com/users/clhermansen"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
Solve a charity's problem with the Julia programming language
|
||||
======
|
||||
See how Julia differs from Java, Python, and Groovy to solve a food
|
||||
bank's real-world problem.
|
||||
See how Julia differs from Java, Python, and Groovy to solve a food bank's real-world problem.
|
||||
|
||||
![Puzzle pieces coming together to form a computer screen][1]
|
||||
|
||||
Image by: Opensource.com
|
||||
|
||||
I have been writing a series of articles about solving a nice, small, and somewhat unusual problem in different programming languages ([Groovy][2], [Python][3], and [Java][4] so far).
|
||||
|
||||
Briefly, the problem is how to unpack bulk supplies into their units (for example, dividing a 10 pack of one-pound bags of your favorite coffee) and repackage them into hampers of similar value to distribute to struggling neighbors in the community.
|
||||
@ -29,10 +31,9 @@ But enough speculation, let's code something!
|
||||
|
||||
### The Julia solution
|
||||
|
||||
My first decision is how to implement the data model. Julia supports _composite types_, seemingly similar to `struct` in C, and Julia even uses the keyword `struct`. Of note is that a `struct` is immutable (unless declared a `mutable struct`), which is fine for this problem since the data doesn't need to be mutated.
|
||||
|
||||
By following the approach I took in the Java solution, the `Unit struct` can be defined as:
|
||||
My first decision is how to implement the data model. Julia supports *composite types*, seemingly similar to `struct` in C, and Julia even uses the keyword `struct`. Of note is that a `struct` is immutable (unless declared a `mutable struct` ), which is fine for this problem since the data doesn't need to be mutated.
|
||||
|
||||
By following the approach I took in the Java solution, the `Unit struct` can be defined as:
|
||||
|
||||
```
|
||||
struct Unit
|
||||
@ -44,13 +45,12 @@ end
|
||||
|
||||
Similarly, `Pack` is defined as the bulk package of `Unit` instances:
|
||||
|
||||
|
||||
```
|
||||
struct Pack
|
||||
unit::Unit
|
||||
count::Int
|
||||
Pack(item, brand, unitCount,p ackPrice) =
|
||||
new(Unit(item, brand, [div][6](packPrice,unitCount)), unitCount)
|
||||
new(Unit(item, brand, div(packPrice,unitCount)), unitCount)
|
||||
end
|
||||
```
|
||||
|
||||
@ -58,7 +58,6 @@ There is an interesting thing here: a Julia "inner constructor." In the Java sol
|
||||
|
||||
Because Julia isn't object-oriented, I can't add methods to `Pack` to give unit price vs. pack price or to unpack it into a list of `Unit` instances. I can declare "getter" functions that accomplish the same tasks. (I probably don't need these, but I'll do it anyway to see how Julia methods work):
|
||||
|
||||
|
||||
```
|
||||
item(pack::Pack) = pack.unit.item
|
||||
brand(pack::Pack) = pack.unit.brand
|
||||
@ -68,17 +67,16 @@ packPrice(pack::Pack) = pack.unit.price * pack.count
|
||||
unpack(pack::Pack) = Iterators.collect(Iterators.repeated(pack.unit,pack.count))
|
||||
```
|
||||
|
||||
The `unpack()` method is quite similar to the method of the same name I declared in the Java class `Pack`. The function `Iterators.repeated(thing,N)` creates an iterator that will deliver `N` copies of `thing`. The `Iterators.collect` (`iterator`) function processes the `iterator` to yield an array made up of the elements it delivers.
|
||||
|
||||
Finally, the `Bought struct`:
|
||||
The `unpack()` method is quite similar to the method of the same name I declared in the Java class `Pack`. The function `Iterators.repeated(thing,N)` creates an iterator that will deliver `N` copies of `thing`. The `Iterators.collect` (`iterator` ) function processes the `iterator` to yield an array made up of the elements it delivers.
|
||||
|
||||
Finally, the `Bought struct` :
|
||||
|
||||
```
|
||||
struct Bought
|
||||
pack::Pack
|
||||
count::Int
|
||||
end
|
||||
unpack(bought::Bought) =
|
||||
unpack(bought::Bought) =
|
||||
Iterators.collect(Iterators.flatten(Iterators.repeated(unpack(bought.pack),
|
||||
bought.count)))
|
||||
```
|
||||
@ -87,7 +85,6 @@ Once again, I'm creating an array of an array of unpacked `Pack` instances (i.e.
|
||||
|
||||
Now I can construct the list of what I bought:
|
||||
|
||||
|
||||
```
|
||||
packs = [
|
||||
Bought(Pack("Rice","Best Family",10,5650),1),
|
||||
@ -111,12 +108,11 @@ I'm starting to see a pattern here… this looks surprisingly like the Java solu
|
||||
|
||||
With the list packs of what I bought, I can now unpack into the units before working on redistributing them:
|
||||
|
||||
|
||||
```
|
||||
`units = Iterators.collect(Iterators.flatten(unpack.(packs)))`
|
||||
units = Iterators.collect(Iterators.flatten(unpack.(packs)))
|
||||
```
|
||||
|
||||
What's going on here? Well, a construct like `unpack.(packs)`—that is, the dot between the function name and the argument list—applies the function `unpack()` to each element in the list `packs`. This will generate a list of lists corresponding to the unpacked groups of `Packs` I bought. To turn that into a flat list of units, I apply `Iterators.flatten()`. Because `Iterators.flatten()` is lazy, to make the flatten thing happen, I wrap it in `Iterators.collect()`. This kind of composition of functions adheres to the spirit of functional programming, even though you don't see the functions chained together, as programmers who write functionally in JavaScript, Java, or what-have-you are familiar with.
|
||||
What's going on here? Well, a construct like `unpack.(packs)` —that is, the dot between the function name and the argument list—applies the function `unpack()` to each element in the list `packs`. This will generate a list of lists corresponding to the unpacked groups of `Packs` I bought. To turn that into a flat list of units, I apply `Iterators.flatten()`. Because `Iterators.flatten()` is lazy, to make the flatten thing happen, I wrap it in `Iterators.collect()`. This kind of composition of functions adheres to the spirit of functional programming, even though you don't see the functions chained together, as programmers who write functionally in JavaScript, Java, or what-have-you are familiar with.
|
||||
|
||||
One observation is that the list of units created here is actually an array whose starting index is 1, not 0.
|
||||
|
||||
@ -124,63 +120,59 @@ With units being the list of units purchased and unpacked, I can now take on rep
|
||||
|
||||
Here's the code, which is not exceptionally different than the versions in Groovy, Python, and Java:
|
||||
|
||||
|
||||
```
|
||||
1 valueIdeal = 5000
|
||||
2 valueMax = round(valueIdeal * 1.1)
|
||||
3 hamperNumber = 0
|
||||
|
||||
4 while length(units) > 0
|
||||
4 while length(units) > 0
|
||||
5 global hamperNumber += 1
|
||||
6 hamper = Unit[]
|
||||
7 value = 0
|
||||
8 canAdd = true
|
||||
9 while canAdd
|
||||
10 u = [rand][7](0:(length(units)-1))
|
||||
10 u = rand(0:(length(units)-1))
|
||||
11 canAdd = false
|
||||
12 for o = 0:(length(units)-1)
|
||||
13 uo = (u + o) % length(units) + 1
|
||||
14 unit = units[uo]
|
||||
15 if length(units) < 3 || findfirst(u -> u == unit,hamper) === nothing && (value + unit.price) < valueMax
|
||||
15 if length(units) < 3 || findfirst(u -> u == unit,hamper) === nothing && (value + unit.price) < valueMax
|
||||
16 push!(hamper,unit)
|
||||
17 value += unit.price
|
||||
18 deleteat!(units,uo)
|
||||
19 canAdd = length(units) > 0
|
||||
19 canAdd = length(units) > 0
|
||||
20 break
|
||||
21 end
|
||||
22 end
|
||||
23 end
|
||||
24 Printf.@[printf][8]("\nHamper %d value %d:\n",hamperNumber,value)
|
||||
24 Printf.@printf("\nHamper %d value %d:\n",hamperNumber,value)
|
||||
25 for unit in hamper
|
||||
26 Printf.@[printf][8]("%-25s%-25s%7d\n",unit.item,unit.brand,unit.price)
|
||||
26 Printf.@printf("%-25s%-25s%7d\n",unit.item,unit.brand,unit.price)
|
||||
27 end
|
||||
28 Printf.@[printf][8]("Remaining units %d\n",length(units))
|
||||
28 Printf.@printf("Remaining units %d\n",length(units))
|
||||
29 end
|
||||
```
|
||||
|
||||
Some clarification, by line numbers:
|
||||
|
||||
* Lines 1–3: Set up the ideal and maximum values to be loaded into any given hamper and initialize Groovy's random number generator and the hamper number
|
||||
* Lines 4–29: This `while` loop redistributes units into hampers, as long as there are more available
|
||||
* Lines 5–7: Increment the (global) hamper number, get a new empty hamper (an array of `Unit` instances), and set its value to 0
|
||||
* Line 8 and 9–23: As long as I can add units to the hamper…
|
||||
* Line 10: Gets a random number between zero and the number of remaining units minus 1
|
||||
* Line 11: Assumes I can't find more units to add
|
||||
* Lines 12–22: This `for` loop, starting at the randomly chosen index, will try to find a unit that can be added to the hamper
|
||||
* Lines 13–14: Figure out which unit to look at (remember arrays start at index 1) and get it
|
||||
* Lines 15–21: I can add this unit to the hamper if there are only a few left or if the value of the hamper isn't too high once the unit is added and if that unit isn't already in the hamper
|
||||
* Lines 16–18: Add the unit to the hamper, increment the hamper value by the unit price, and remove the unit from the available units list
|
||||
* Lines 19–20: As long as there are units left, I can add more, so break out of this loop to keep looking
|
||||
* Line 22: On exit from this `for` loop, if I have inspected every remaining unit and could not find one to add to the hamper, the hamper is complete; otherwise, I found one and can continue looking for more
|
||||
* Line 23: On exit from this `while` loop, the hamper is as full as I can make it, so…
|
||||
* Lines 24–28: Print out the contents of the hamper and the remaining units info
|
||||
* Line 29: When I exit this loop, there are no more units left
|
||||
|
||||
|
||||
* Lines 1–3: Set up the ideal and maximum values to be loaded into any given hamper and initialize Groovy's random number generator and the hamper number
|
||||
* Lines 4–29: This `while` loop redistributes units into hampers, as long as there are more available
|
||||
* Lines 5–7: Increment the (global) hamper number, get a new empty hamper (an array of `Unit` instances), and set its value to 0
|
||||
* Line 8 and 9–23: As long as I can add units to the hamper…
|
||||
* Line 10: Gets a random number between zero and the number of remaining units minus 1
|
||||
* Line 11: Assumes I can't find more units to add
|
||||
* Lines 12–22: This `for` loop, starting at the randomly chosen index, will try to find a unit that can be added to the hamper
|
||||
* Lines 13–14: Figure out which unit to look at (remember arrays start at index 1) and get it
|
||||
* Lines 15–21: I can add this unit to the hamper if there are only a few left or if the value of the hamper isn't too high once the unit is added and if that unit isn't already in the hamper
|
||||
* Lines 16–18: Add the unit to the hamper, increment the hamper value by the unit price, and remove the unit from the available units list
|
||||
* Lines 19–20: As long as there are units left, I can add more, so break out of this loop to keep looking
|
||||
* Line 22: On exit from this `for` loop, if I have inspected every remaining unit and could not find one to add to the hamper, the hamper is complete; otherwise, I found one and can continue looking for more
|
||||
* Line 23: On exit from this `while` loop, the hamper is as full as I can make it, so…
|
||||
* Lines 24–28: Print out the contents of the hamper and the remaining units info
|
||||
* Line 29: When I exit this loop, there are no more units left
|
||||
|
||||
The output of running this code looks quite similar to the output from the other programs:
|
||||
|
||||
|
||||
```
|
||||
Hamper 1 value 5020:
|
||||
Tea Superior 544
|
||||
@ -238,9 +230,8 @@ Once again, the random-number-driven list manipulation seems to make the "workin
|
||||
|
||||
Given that the main effort revolves around `for` and `while` loops, in Julia, I don't see any construct similar to:
|
||||
|
||||
|
||||
```
|
||||
`for (boolean canAdd = true; canAdd; ) { … }`
|
||||
for (boolean canAdd = true; canAdd; ) { … }
|
||||
```
|
||||
|
||||
This means I have to declare the `canAdd` variable outside the `while` loop. Which is too bad—but not a terrible thing.
|
||||
@ -249,27 +240,24 @@ I do miss not being able to attach behavior directly to my data, but that's just
|
||||
|
||||
Good things: low ceremony, check; decent list-handling, check; compact and readable code, check. All in all, a pleasant experience, supporting the idea that Julia can be a decent choice to solve "ordinary problems" and as a scripting language.
|
||||
|
||||
Next time, I'll do this exercise in [Go][9].
|
||||
Next time, I'll do this exercise in [Go][6].
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/1/solve-problem-julia
|
||||
|
||||
作者:[Chris Hermansen][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/clhermansen
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/puzzle_computer_solve_fix_tool.png?itok=U0pH1uwj (Puzzle pieces coming together to form a computer screen)
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://opensource.com/sites/default/files/lead-images/puzzle_computer_solve_fix_tool.png
|
||||
[2]: https://opensource.com/article/20/9/groovy
|
||||
[3]: https://opensource.com/article/20/9/solve-problem-python
|
||||
[4]: https://opensource.com/article/20/9/problem-solving-java
|
||||
[5]: https://julialang.org/
|
||||
[6]: http://www.opengroup.org/onlinepubs/009695399/functions/div.html
|
||||
[7]: http://www.opengroup.org/onlinepubs/009695399/functions/rand.html
|
||||
[8]: http://www.opengroup.org/onlinepubs/009695399/functions/printf.html
|
||||
[9]: https://golang.org/
|
||||
[6]: https://golang.org/
|
||||
|
@ -1,21 +1,23 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (How to customize your voice assistant with the voice of your choice)
|
||||
[#]: via: (https://opensource.com/article/21/1/customize-voice-assistant)
|
||||
[#]: author: (Rich Lucente https://opensource.com/users/rlucente)
|
||||
[#]: subject: "How to customize your voice assistant with the voice of your choice"
|
||||
[#]: via: "https://opensource.com/article/21/1/customize-voice-assistant"
|
||||
[#]: author: "Rich Lucente https://opensource.com/users/rlucente"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
How to customize your voice assistant with the voice of your choice
|
||||
======
|
||||
The Nana and Poppy project enables a voice assistant to greet users with
|
||||
their great-grandchildren's voices instead of a generic AI.
|
||||
The Nana and Poppy project enables a voice assistant to greet users with their great-grandchildren's voices instead of a generic AI.
|
||||
|
||||
![radio communication signals][1]
|
||||
|
||||
Image by: [Internet Archive Book Images][2]. Modified by Opensource.com. [CC BY-SA 4.0][3]
|
||||
|
||||
It can be hard to find meaningful gifts for relatives that already have almost everything. My wife and I have given our parents "experiences" to try something novel, such as going to a themed restaurant or seeing a concert, but as our parents get older, it becomes more difficult. This year was no exception—until I thought about a way open source could give them something really special.
|
||||
|
||||
What if when they request help from an artificial intelligence (AI) voice assistant such as [Mycroft][2], my in-laws could get a special greeting? I looked at the existing voice assistant APIs to see if something like this was already available. There was something close, but not exactly what I was looking for. My idea was to record their great-grandchildren speaking a short greeting that would play whenever they push the button and before the conversation with the voice assistant begins. The greeting would be something like:
|
||||
What if when they request help from an artificial intelligence (AI) voice assistant such as [Mycroft][4], my in-laws could get a special greeting? I looked at the existing voice assistant APIs to see if something like this was already available. There was something close, but not exactly what I was looking for. My idea was to record their great-grandchildren speaking a short greeting that would play whenever they push the button and before the conversation with the voice assistant begins. The greeting would be something like:
|
||||
|
||||
> "Good morning, Nana and Poppy. Today is December 25th. The time is 3:10 pm. The current temperature for Waynesboro is 47 degrees. The current temperature for Ocean City is 50 degrees."
|
||||
|
||||
@ -25,168 +27,166 @@ When they press the button, my in-laws would hear their great-grandchildren repo
|
||||
|
||||
The first problem was figuring out what phrases the voice assistant would need to say. Thinking about all the dates, times, and temperatures that I would need to cover, I arrived at a list of 79 phrases. I sent these instructions to my nieces:
|
||||
|
||||
> _Please record the kids saying each line below. Sorry there are so many. It's okay to do this in one setting with prompting them if it makes it easier. I can edit the audio files and deal with most formats, so none of that should be a problem. Just record using your phone in whatever way is easiest._
|
||||
>
|
||||
> _Make sure that the kids say each line clearly and loudly. There should be a slight pause between each line to make editing easier (prompting helps like "Repeat after me …"). That will make it easier for me to chop these up into individual sound files._
|
||||
>
|
||||
> _Whenever the button on the device is pushed, it will respond with a random grandchild saying the correct date/time/temperature, like:_
|
||||
>
|
||||
> _"Good afternoon, Nana and Poppy. Today is January third. The time is one oh four pm. The current temperature for Waynesboro is thirty degrees. The current temperature for Ocean City is thirty four degrees."_
|
||||
>
|
||||
> _PLEASE RECORD EACH CHILD SAYING THE FOLLOWING PHRASES WITH A SHORT PAUSE BETWEEN EACH ONE:_
|
||||
> Please record the kids saying each line below. Sorry there are so many. It's okay to do this in one setting with prompting them if it makes it easier. I can edit the audio files and deal with most formats, so none of that should be a problem. Just record using your phone in whatever way is easiest.
|
||||
|
||||
> Make sure that the kids say each line clearly and loudly. There should be a slight pause between each line to make editing easier (prompting helps like "Repeat after me …"). That will make it easier for me to chop these up into individual sound files.*
|
||||
|
||||
> Whenever the button on the device is pushed, it will respond with a random grandchild saying the correct date/time/temperature, like: "Good afternoon, Nana and Poppy. Today is January third. The time is one oh four pm. The current temperature for Waynesboro is thirty degrees. The current temperature for Ocean City is thirty four degrees."
|
||||
|
||||
> PLEASE RECORD EACH CHILD SAYING THE FOLLOWING PHRASES WITH A SHORT PAUSE BETWEEN EACH ONE:
|
||||
|
||||
Then I provided the following list of words for the children to record:
|
||||
|
||||
_Good
|
||||
morning
|
||||
afternoon
|
||||
evening
|
||||
night_
|
||||
(这个表格有问题,待修复)
|
||||
|
||||
_Nana and Poppy_
|
||||
| - | - | - |
|
||||
| :- | :- | :- |
|
||||
| Good
|
||||
morning
|
||||
afternoon
|
||||
evening
|
||||
night
|
||||
Nana and Poppy
|
||||
The time
|
||||
Today
|
||||
The current temperature for
|
||||
Waynesboro
|
||||
Ocean City
|
||||
is
|
||||
and
|
||||
degrees
|
||||
minus
|
||||
am
|
||||
pm
|
||||
January
|
||||
February
|
||||
March
|
||||
April
|
||||
May | June
|
||||
July
|
||||
August
|
||||
September
|
||||
October
|
||||
November
|
||||
December
|
||||
first
|
||||
second
|
||||
third
|
||||
fourth
|
||||
fifth
|
||||
sixth
|
||||
seventh
|
||||
eighth
|
||||
ninth
|
||||
tenth
|
||||
eleventh
|
||||
twelfth
|
||||
thirteenth
|
||||
fourteenth
|
||||
fifteenth
|
||||
sixteenth
|
||||
seventeenth
|
||||
eighteenth
|
||||
nineteenth
|
||||
twentieth
|
||||
thirtieth
|
||||
oh | one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
nine
|
||||
ten
|
||||
eleven
|
||||
twelve
|
||||
thirteen
|
||||
fourteen
|
||||
fifteen
|
||||
sixteen
|
||||
seventeen
|
||||
eighteen
|
||||
nineteen
|
||||
twenty
|
||||
thirty
|
||||
forty
|
||||
fifty
|
||||
sixty
|
||||
seventy
|
||||
eighty
|
||||
ninety
|
||||
hundred |
|
||||
|
||||
_The time
|
||||
Today
|
||||
The current temperature for
|
||||
Waynesboro
|
||||
Ocean City
|
||||
is
|
||||
and
|
||||
degrees
|
||||
minus_
|
||||
*Nana and Poppy*
|
||||
|
||||
_am
|
||||
pm_
|
||||
*The time
|
||||
Today
|
||||
The current temperature for
|
||||
Waynesboro
|
||||
Ocean City
|
||||
is
|
||||
and
|
||||
degrees
|
||||
minus*
|
||||
|
||||
_January
|
||||
February
|
||||
March
|
||||
April
|
||||
May_
|
||||
*am
|
||||
pm*
|
||||
|
||||
| _June
|
||||
July
|
||||
August
|
||||
September
|
||||
October
|
||||
November
|
||||
December
|
||||
first
|
||||
second
|
||||
third
|
||||
fourth
|
||||
fifth
|
||||
sixth
|
||||
seventh
|
||||
eighth
|
||||
ninth
|
||||
tenth
|
||||
eleventh
|
||||
twelfth
|
||||
thirteenth
|
||||
fourteenth
|
||||
fifteenth
|
||||
sixteenth
|
||||
seventeenth
|
||||
eighteenth
|
||||
nineteenth
|
||||
twentieth
|
||||
thirtieth
|
||||
oh_ | _one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
nine
|
||||
ten
|
||||
eleven
|
||||
twelve
|
||||
thirteen
|
||||
fourteen
|
||||
fifteen
|
||||
sixteen
|
||||
seventeen
|
||||
eighteen
|
||||
nineteen
|
||||
twenty
|
||||
thirty
|
||||
forty
|
||||
fifty
|
||||
sixty
|
||||
seventy
|
||||
eighty
|
||||
ninety
|
||||
hundred_
|
||||
---|---|---
|
||||
*January
|
||||
February
|
||||
March
|
||||
April
|
||||
May*
|
||||
|
||||
My nieces are doubly blessed with children under 10 years old and near-infinite patience. So, after a couple of months of prodding, I received a three-minute audio file for each child.
|
||||
|
||||
Now my problem was how to edit them. I needed to normalize the recordings, reduce noise, and chop them into audio clips for individual words and phrases. I also wanted to take advantage of lossless audio, and I decided to convert the tracks to Waveform Audio File Format ([WAV][3]). Audacity was just the open source tool to do all of that.
|
||||
Now my problem was how to edit them. I needed to normalize the recordings, reduce noise, and chop them into audio clips for individual words and phrases. I also wanted to take advantage of lossless audio, and I decided to convert the tracks to Waveform Audio File Format ([WAV][5]). Audacity was just the open source tool to do all of that.
|
||||
|
||||
### Audacity to the rescue!
|
||||
|
||||
[Audacity][4] is a feature-rich open source sound-editing tool. The software's features and capabilities can be overwhelming, so I'll describe the workflow I followed to accomplish my goals. I make no claims to being an Audacity expert, but the steps I followed seemed to work pretty well. (Comments are always welcome on how to improve what I've done.)
|
||||
[Audacity][6] is a feature-rich open source sound-editing tool. The software's features and capabilities can be overwhelming, so I'll describe the workflow I followed to accomplish my goals. I make no claims to being an Audacity expert, but the steps I followed seemed to work pretty well. (Comments are always welcome on how to improve what I've done.)
|
||||
|
||||
Audacity has [downloads][5] for Linux, Windows, and macOS. I grabbed the most recent macOS binary and quickly installed it on my laptop. Launching Audacity opens an empty new project. I imported all of the children's audio files using the **Import** feature.
|
||||
Audacity has [downloads][7] for Linux, Windows, and macOS. I grabbed the most recent macOS binary and quickly installed it on my laptop. Launching Audacity opens an empty new project. I imported all of the children's audio files using the **Import** feature.
|
||||
|
||||
![Import audio files in Audacity][6]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Import audio files in Audacity][8]
|
||||
|
||||
#### Normalizing audio files
|
||||
|
||||
Some of the children spoke louder than others, so the various audio files had different volume levels. I needed to normalize the audio tracks so that the greeting's volume would be the same regardless of which child was speaking. To normalize the volumes, I began by selecting all of the audio tracks after they were imported.
|
||||
|
||||
![Selecting all the audio tracks][8]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Selecting all the audio tracks][9]
|
||||
|
||||
To normalize the children's peaks and valleys, so one child wasn't louder than the other, I used Audacity's **Normalize** effect.
|
||||
|
||||
![Normalize effect][9]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Normalize effect][10]
|
||||
|
||||
It's important to understand that the Normalize and Amplify effects do very different things. Normalize adjusts the highest peaks and lowest valleys for multiple tracks, so they are all similar, whereas Amplify exaggerates the existing peaks and valleys. If I had used Amplify instead of Normalize, the louder child would have become even louder. I used the default settings to normalize the two audio tracks.
|
||||
|
||||
![Normalize defaults][10]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Normalize defaults][11]
|
||||
|
||||
#### Remove background noise
|
||||
|
||||
Another thing I noticed is that there was noise between the spoken phrases on the tracks. Audacity has tooling to help reduce background noise and result in much cleaner audio. To reduce noise, select a sample of an audio track with background noise. I used the **View->Zoom** menu option to see the track's noise more easily.
|
||||
Another thing I noticed is that there was noise between the spoken phrases on the tracks. Audacity has tooling to help reduce background noise and result in much cleaner audio. To reduce noise, select a sample of an audio track with background noise. I used the **View->Zoom** menu option to see the track's noise more easily.
|
||||
|
||||
![Background noise sample][11]
|
||||
![Background noise sample][12]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
To make sure I selected only the background noise, I listened to the selected audio clip using the **Play** button in the toolbar. Next, I selected **Effect->Noise Reduction**.
|
||||
|
||||
To make sure I selected only the background noise, I listened to the selected audio clip using the **Play** button in the toolbar. Next, I selected **Effect->Noise Reduction**.
|
||||
|
||||
![Noise Reduction effect][12]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Noise Reduction effect][13]
|
||||
|
||||
Then I created a **Noise Profile** using step 1 in the **Noise Reduction** dialog.
|
||||
|
||||
![Get a Noise Profile from audio sample][13]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Get a Noise Profile from audio sample][14]
|
||||
|
||||
Audacity characterizes the background noise in the audio sample so that it can be removed. To remove the background noise, I selected the entire audio track by pressing the small **Select** button to the left of the track.
|
||||
|
||||
![Select whole audio track button][14]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Select whole audio track button][15]
|
||||
|
||||
I applied the **Noise Reduction** effect again, but this time I pressed **OK** in step 2 of the dialog. I accepted the default settings.
|
||||
|
||||
![Noise Reduction effect step 2][15]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Noise Reduction effect step 2][16]
|
||||
|
||||
I repeated these steps for each child's audio track, so I had normalized audio tracks, and the background noise was characterized and removed.
|
||||
|
||||
@ -194,110 +194,106 @@ I repeated these steps for each child's audio track, so I had normalized audio t
|
||||
|
||||
The remaining task was to zoom and scroll through each track and export the specific clips as separate audio files in WAV format. When working with one child's track, I needed to mute the other tracks using either the small **Mute** button to the left of each audio track or, since there were so many tracks, selecting the **Solo** button for the track I wanted to work with.
|
||||
|
||||
![Mute and Solo buttons for multiple tracks][16]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Mute and Solo buttons for multiple tracks][17]
|
||||
|
||||
Selecting each word and phrase can be tricky, but the ability to zoom into an audio track was my friend. I tried to set each audio clip's start and end to just before and just after the word or phrase being spoken. Before exporting any audio clips, I played the selected clip using the **Play** icon on the toolbar to make sure I got it all.
|
||||
|
||||
One interesting thing is how waveforms map to spoken words. The waveforms for "six" and "sixth" are incredibly similar, with the latter having a smaller audio waveform to the right for the "th" sound. I carefully tested each clip before exporting it to make sure I had captured the full word or phrase.
|
||||
|
||||
After selecting an audio clip for a word or phrase, I exported the selected audio using the **File->Export** menu.
|
||||
After selecting an audio clip for a word or phrase, I exported the selected audio using the **File->Export** menu.
|
||||
|
||||
![Exporting selected audio][17]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Exporting selected audio][18]
|
||||
|
||||
I had to make sure to save each clip using the correct file name from the list of words and phrases. This is because the application I used to customize the voice assistant expects the file name to match an entry in the phrase list.
|
||||
|
||||
The expected file names for the audio clips (without the .wav extension) are listed below. Note the underscores within the phrases. If you're doing this project, adjust the bold file names to match your loved ones' nicknames and location preferences. You'll also have to make the same changes in the application source code.
|
||||
|
||||
_good
|
||||
morning
|
||||
afternoon
|
||||
evening
|
||||
night
|
||||
**nana_and_poppy**
|
||||
the_time
|
||||
today
|
||||
the_current_temperature_for
|
||||
**waynesboro
|
||||
ocean_city**
|
||||
is
|
||||
and
|
||||
degrees
|
||||
minus
|
||||
am
|
||||
pm
|
||||
january
|
||||
february
|
||||
march
|
||||
april
|
||||
may
|
||||
june
|
||||
july
|
||||
august
|
||||
september
|
||||
october_ | _november
|
||||
december
|
||||
first
|
||||
second
|
||||
third
|
||||
fourth
|
||||
fifth
|
||||
sixth
|
||||
seventh
|
||||
eighth
|
||||
ninth
|
||||
tenth
|
||||
eleventh
|
||||
twelfth
|
||||
thirteenth
|
||||
fourteenth
|
||||
fifteenth
|
||||
sixteenth
|
||||
seventeenth
|
||||
eighteenth
|
||||
nineteenth
|
||||
twentieth
|
||||
thirtieth
|
||||
oh
|
||||
one
|
||||
two_ | _three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
nine
|
||||
ten
|
||||
eleven
|
||||
twelve
|
||||
thirteen
|
||||
fourteen
|
||||
fifteen
|
||||
sixteen
|
||||
seventeen
|
||||
eighteen
|
||||
nineteen
|
||||
twenty
|
||||
thirty
|
||||
forty
|
||||
fifty
|
||||
sixty
|
||||
seventy
|
||||
eighty
|
||||
ninety
|
||||
hundred_
|
||||
---|---|---
|
||||
(这个表格有问题,待修正)
|
||||
| - | - | - |
|
||||
| :- | :- | :- |
|
||||
| good
|
||||
morning
|
||||
afternoon
|
||||
evening
|
||||
night
|
||||
nana_and_poppy
|
||||
the_time
|
||||
today
|
||||
the_current_temperature_for
|
||||
waynesboro
|
||||
ocean_city
|
||||
is
|
||||
and
|
||||
degrees
|
||||
minus
|
||||
am
|
||||
pm
|
||||
january
|
||||
february
|
||||
march
|
||||
april
|
||||
may
|
||||
june
|
||||
july
|
||||
august
|
||||
september
|
||||
october | november
|
||||
december
|
||||
first
|
||||
second
|
||||
third
|
||||
fourth
|
||||
fifth
|
||||
sixth
|
||||
seventh
|
||||
eighth
|
||||
ninth
|
||||
tenth
|
||||
eleventh
|
||||
twelfth
|
||||
thirteenth
|
||||
fourteenth
|
||||
fifteenth
|
||||
sixteenth
|
||||
seventeenth
|
||||
eighteenth
|
||||
nineteenth
|
||||
twentieth
|
||||
thirtieth
|
||||
oh
|
||||
one
|
||||
two | three
|
||||
four
|
||||
five
|
||||
six
|
||||
seven
|
||||
eight
|
||||
nine
|
||||
ten
|
||||
eleven
|
||||
twelve
|
||||
thirteen
|
||||
fourteen
|
||||
fifteen
|
||||
sixteen
|
||||
seventeen
|
||||
eighteen
|
||||
nineteen
|
||||
twenty
|
||||
thirty
|
||||
forty
|
||||
fifty
|
||||
sixty
|
||||
seventy
|
||||
eighty
|
||||
ninety
|
||||
hundred |
|
||||
|
||||
This project's GitHub repository also includes a Bash script to run as a sanity check for any missing or misnamed files.
|
||||
|
||||
After choosing each clip's appropriate name, I saved the clip in the child's specific folder (child1, child2, etc.) as a WAV format file. I accepted the default export settings.
|
||||
|
||||
![Converting clip to WAV format][18]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Converting clip to WAV format][19]
|
||||
|
||||
After exporting all the audio clips, I had a folder for each child that was fully populated with WAV files for the phrases above. This seems like a lot of work, but it took only about 90 minutes for each child, and I got way more efficient with each successive audio clip.
|
||||
|
||||
@ -305,22 +301,19 @@ After exporting all the audio clips, I had a folder for each child that was full
|
||||
|
||||
Now that I had the audio clips for the greeting, I needed to think about the application and how to package it. I also wanted an open source-friendly solution that was open to modification.
|
||||
|
||||
About two years ago, a colleague gave me a [Google AIY Voice Kit][19] that he grabbed from the clearance bin for just $10. It's a cleverly folded box containing a speaker, microphone, and custom circuit board. You supply a Raspberry Pi and quickly have a do-it-yourself Google voice assistant. These kits are available for purchase online and in electronics stores. This small box offered an easy way to package the project.
|
||||
About two years ago, a colleague gave me a [Google AIY Voice Kit][20] that he grabbed from the clearance bin for just $10. It's a cleverly folded box containing a speaker, microphone, and custom circuit board. You supply a Raspberry Pi and quickly have a do-it-yourself Google voice assistant. These kits are available for purchase online and in electronics stores. This small box offered an easy way to package the project.
|
||||
|
||||
![Google AIY Voice Kit][20]
|
||||
|
||||
(Rich Lucente, [CC BY-SA 4.0][7])
|
||||
![Google AIY Voice Kit][21]
|
||||
|
||||
### Customize the voice assistant
|
||||
|
||||
The Google kit includes a Python API and several Python modules. I followed the kit's instructions to get the initial configuration working. The [Google Assistant gRPC][21] software is open source under an Apache 2.0 license.
|
||||
The Google kit includes a Python API and several Python modules. I followed the kit's instructions to get the initial configuration working. The [Google Assistant gRPC][22] software is open source under an Apache 2.0 license.
|
||||
|
||||
I adapted the Google Assistant gRPC demo to implement my application. The application's operation is fairly simple: First, it waits for the device's button to be pressed. The code then constructs four separate word lists for: 1. the greeting and date, 2. the current time, 3. the current temperature of the first location, and 4. the current temperature of the second location. The children's voices are randomly shuffled, and then each word list is used to play the audio clips corresponding to the child assigned to that list. (This is why it was important to strictly follow the naming convention for the audio clips.) The application then initiates a conversation with the Google Assistant API.
|
||||
|
||||
At first, I thought the code to gather weather data for the current temperature and convert numbers to words would be challenging. This proved not to be the case at all. In fact, existing open source Python modules made it all simple and intuitive.
|
||||
|
||||
There were two cases to be addressed for converting numbers to word lists: I needed to convert ordinal numbers to words (e.g., 1 and 2 to first and second), and I also needed to convert cardinal numbers to words (e.g., 28 to twenty-eight). The open source [inflect.py module][22] has functions that handle both cases quite easily.
|
||||
|
||||
There were two cases to be addressed for converting numbers to word lists: I needed to convert ordinal numbers to words (e.g., 1 and 2 to first and second), and I also needed to convert cardinal numbers to words (e.g., 28 to twenty-eight). The open source [inflect.py module][23] has functions that handle both cases quite easily.
|
||||
|
||||
```
|
||||
import inflect
|
||||
@ -337,8 +330,7 @@ print(p.number_to_words(p.ordinal(number)).replace('-', ' ').split(' '))
|
||||
|
||||
The inflect engine returns string representations of the numbers with embedded hyphens (e.g., twenty-three) so that the code splits the strings into variable-length word lists by converting the hyphens to spaces and splitting the string into a list using a space as the delimiter.
|
||||
|
||||
The next problem to solve was getting the current temperature for the two locations. [Open Weather Map][23] offers a free-tier weather service that allows up to 60 calls a minute or 1 million calls a month, which is way more than this project needs. I signed up for the free-tier service and received an API key. It was very easy to access the service by using the open source Python wrapper module [PyOWM][24]. Here is a simplified code snippet:
|
||||
|
||||
The next problem to solve was getting the current temperature for the two locations. [Open Weather Map][24] offers a free-tier weather service that allows up to 60 calls a minute or 1 million calls a month, which is way more than this project needs. I signed up for the free-tier service and received an API key. It was very easy to access the service by using the open source Python wrapper module [PyOWM][25]. Here is a simplified code snippet:
|
||||
|
||||
```
|
||||
import pyowm
|
||||
@ -359,46 +351,49 @@ temp = round(observation.weather.temperature('fahrenheit')['temp'])
|
||||
|
||||
### Wrapping it up with a bow
|
||||
|
||||
The full source code for the project is available in my [GitHub repository][25]. The project includes a systemd service unit file adapted from Google's demo to automatically start the application on device boot. The GitHub repository includes instructions to install the Python modules and configure the systemd service.
|
||||
The full source code for the project is available in my [GitHub repository][26]. The project includes a systemd service unit file adapted from Google's demo to automatically start the application on device boot. The GitHub repository includes instructions to install the Python modules and configure the systemd service.
|
||||
|
||||
I created a [short video][26] of the result. Five custom voice assistants were distributed during the holidays: one each for the great grandparents and grandparents of each child. For some, these gifts brought tears of joy. The children's voices are absolutely adorable and these boxes capture a fleeting moment of childhood that can be enjoyed for a very long time.
|
||||
I created a [short video][27] of the result. Five custom voice assistants were distributed during the holidays: one each for the great grandparents and grandparents of each child. For some, these gifts brought tears of joy. The children's voices are absolutely adorable and these boxes capture a fleeting moment of childhood that can be enjoyed for a very long time.
|
||||
|
||||
Image by: (Rich Lucente, CC BY-SA 4.0)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/1/customize-voice-assistant
|
||||
|
||||
作者:[Rich Lucente][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/rlucente
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/sound-radio-noise-communication.png?itok=KMNn9QrZ (radio communication signals)
|
||||
[2]: https://opensource.com/article/20/7/mycroft-voice-skill
|
||||
[3]: https://en.wikipedia.org/wiki/WAV
|
||||
[4]: https://www.audacityteam.org/
|
||||
[5]: https://www.audacityteam.org/download/
|
||||
[6]: https://opensource.com/sites/default/files/uploads/audacity1_importaudio.png (Import audio files in Audacity)
|
||||
[7]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[8]: https://opensource.com/sites/default/files/uploads/audacity2_selectingtracks.png (Selecting all the audio tracks)
|
||||
[9]: https://opensource.com/sites/default/files/uploads/audacity3_normalize.png (Normalize effect)
|
||||
[10]: https://opensource.com/sites/default/files/uploads/audacity4_normalizedefaults.png (Normalize defaults)
|
||||
[11]: https://opensource.com/sites/default/files/uploads/audacity5_backgroundnoise.png (Background noise sample)
|
||||
[12]: https://opensource.com/sites/default/files/uploads/audacity6_noisereduction.png (Noise Reduction effect)
|
||||
[13]: https://opensource.com/sites/default/files/uploads/audacity7_noiseprofile.png (Get a Noise Profile from audio sample)
|
||||
[14]: https://opensource.com/sites/default/files/uploads/audacity8_selecttrack.png (Select whole audio track button)
|
||||
[15]: https://opensource.com/sites/default/files/uploads/audacity9_noisereduction2.png (Noise Reduction effect step 2)
|
||||
[16]: https://opensource.com/sites/default/files/uploads/audacity10_mutesolo.png (Mute and Solo buttons for multiple tracks)
|
||||
[17]: https://opensource.com/sites/default/files/uploads/audacity11_exportaudio.png (Exporting selected audio)
|
||||
[18]: https://opensource.com/sites/default/files/uploads/audacity12_convertwav.png (Converting clip to WAV format)
|
||||
[19]: https://aiyprojects.withgoogle.com/voice/
|
||||
[20]: https://opensource.com/sites/default/files/uploads/googleaiy.png (Google AIY Voice Kit)
|
||||
[21]: https://pypi.org/project/google-assistant-grpc/
|
||||
[22]: https://pypi.org/project/inflect
|
||||
[23]: https://openweathermap.org/
|
||||
[24]: https://pypi.org/project/pyowm
|
||||
[25]: https://github.com/rlucente-se-jboss/nana-poppy-project
|
||||
[26]: https://youtu.be/Co7rigJRNUM
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://opensource.com/sites/default/files/lead-images/sound-radio-noise-communication.png
|
||||
[2]: https://www.flickr.com/photos/internetarchivebookimages/14571450820/in/photolist-ocCuEG-otg1AX-hPy8JE-oc9YmN-oeUU2C-8cKWej-hQz72S-rpae2k-ocNYbT-oxbPTB-odGRsQ-ouDBo1-i5GTL8-qscJfA-idDrfk-i5D6oK-6K6iNH-ouxpn7-i8SivQ-oeY1eG-i7HGbT-bqXPhH-hN5on7-i9Q8YD-ouFYDw-fpy7Lo-oeSJo1-otqUu4-hNaVhf-oydqAV-owur2M-owkTSD-oydSWR-ocayce-ovFdYk-ocdaeL-ouE9UP-zmmrhp-qxtozB-ouqnSQ-obYbwS-odrnXt-ousXXw-ocA6Uo-owme9S-ouACY2-ocajY1-oeUJQG-ouryBk-ouxMJb
|
||||
[3]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[4]: https://opensource.com/article/20/7/mycroft-voice-skill
|
||||
[5]: https://en.wikipedia.org/wiki/WAV
|
||||
[6]: https://www.audacityteam.org/
|
||||
[7]: https://www.audacityteam.org/download/
|
||||
[8]: https://opensource.com/sites/default/files/uploads/audacity1_importaudio.png
|
||||
[9]: https://opensource.com/sites/default/files/uploads/audacity2_selectingtracks.png
|
||||
[10]: https://opensource.com/sites/default/files/uploads/audacity3_normalize.png
|
||||
[11]: https://opensource.com/sites/default/files/uploads/audacity4_normalizedefaults.png
|
||||
[12]: https://opensource.com/sites/default/files/uploads/audacity5_backgroundnoise.png
|
||||
[13]: https://opensource.com/sites/default/files/uploads/audacity6_noisereduction.png
|
||||
[14]: https://opensource.com/sites/default/files/uploads/audacity7_noiseprofile.png
|
||||
[15]: https://opensource.com/sites/default/files/uploads/audacity8_selecttrack.png
|
||||
[16]: https://opensource.com/sites/default/files/uploads/audacity9_noisereduction2.png
|
||||
[17]: https://opensource.com/sites/default/files/uploads/audacity10_mutesolo.png
|
||||
[18]: https://opensource.com/sites/default/files/uploads/audacity11_exportaudio.png
|
||||
[19]: https://opensource.com/sites/default/files/uploads/audacity12_convertwav.png
|
||||
[20]: https://aiyprojects.withgoogle.com/voice/
|
||||
[21]: https://opensource.com/sites/default/files/uploads/googleaiy.png
|
||||
[22]: https://pypi.org/project/google-assistant-grpc/
|
||||
[23]: https://pypi.org/project/inflect
|
||||
[24]: https://openweathermap.org/
|
||||
[25]: https://pypi.org/project/pyowm
|
||||
[26]: https://github.com/rlucente-se-jboss/nana-poppy-project
|
||||
[27]: https://youtu.be/Co7rigJRNUM
|
||||
|
@ -1,27 +1,28 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (A hands-on tutorial for using the GNU Project Debugger)
|
||||
[#]: via: (https://opensource.com/article/21/1/gnu-project-debugger)
|
||||
[#]: author: (Stephan Avenwedde https://opensource.com/users/hansic99)
|
||||
[#]: subject: "A hands-on tutorial for using the GNU Project Debugger"
|
||||
[#]: via: "https://opensource.com/article/21/1/gnu-project-debugger"
|
||||
[#]: author: "Stephan Avenwedde https://opensource.com/users/hansic99"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
A hands-on tutorial for using the GNU Project Debugger
|
||||
======
|
||||
The GNU Project Debugger is a powerful tool for finding bugs in
|
||||
programs.
|
||||
The GNU Project Debugger is a powerful tool for finding bugs in programs.
|
||||
|
||||
![magnifying glass on computer screen, finding a bug in the code][1]
|
||||
|
||||
Image by: Opensource.com
|
||||
|
||||
If you're a programmer and you want to put a certain functionality in your software, you start by thinking of ways to implement it—such as writing a method, defining a class, or creating new data types. Then you write the implementation in a language that the compiler or interpreter can understand. But what if the compiler or interpreter does not understand the instructions as you had them in mind, even though you're sure you did everything right? What if the software works fine most of the time but causes bugs in certain circumstances? In these cases, you have to know how to use a debugger correctly to find the source of your troubles.
|
||||
|
||||
The GNU Project Debugger ([GDB][2]) is a powerful tool for finding bugs in programs. It helps you uncover the reason for an error or crash by tracking what is going on inside the program during execution.
|
||||
|
||||
This article is a hands-on tutorial on basic GDB usage. To follow along with the examples, open the command line and clone this repository:
|
||||
|
||||
|
||||
```
|
||||
`git clone https://github.com/hANSIc99/core_dump_example.git`
|
||||
git clone https://github.com/hANSIc99/core_dump_example.git
|
||||
```
|
||||
|
||||
### Shortcuts
|
||||
@ -30,7 +31,7 @@ Every command in GDB can be shortened. For example, `info break`, which shows th
|
||||
|
||||
### Command-line parameters
|
||||
|
||||
You can attach GDB to every executable. Navigate to the repository you cloned, and compile it by running `make`. You should now have an executable called **coredump**. (See my article on [_Creating and debugging Linux dump files_][3] for more information..
|
||||
You can attach GDB to every executable. Navigate to the repository you cloned, and compile it by running `make`. You should now have an executable called **coredump**. (See my article on [Creating and debugging Linux dump files][3] for more information..
|
||||
|
||||
To attach GDB to the executable, type: `gdb coredump`.
|
||||
|
||||
@ -38,87 +39,66 @@ Your output should look like this:
|
||||
|
||||
![gdb coredump output][4]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
It says no debugging symbols were found.
|
||||
|
||||
Debugging information is part of the object file (the executable) and includes data types, function signatures, and the relationship between the source code and the opcode. At this point, you have two options:
|
||||
|
||||
* Continue debugging the assembly (see "[Debug without symbols][6]" below)
|
||||
* Compile with debug information using the information in the next section
|
||||
|
||||
|
||||
* Continue debugging the assembly (see "Debug without symbols" below)
|
||||
* Compile with debug information using the information in the next section
|
||||
|
||||
### Compile with debug information
|
||||
|
||||
To include debug information in the binary file, you have to recompile it. Open the **Makefile** and remove the hashtag (`#`) from line 9:
|
||||
|
||||
To include debug information in the binary file, you have to recompile it. Open the **Makefile** and remove the hashtag (`#` ) from line 9:
|
||||
|
||||
```
|
||||
`CFLAGS =-Wall -Werror -std=c++11 -g`
|
||||
CFLAGS =-Wall -Werror -std=c++11 -g
|
||||
```
|
||||
|
||||
The `g` option tells the compiler to include the debug information. Run `make clean` followed by `make` and invoke GDB again. You should get this output and can start debugging the code:
|
||||
|
||||
![GDB output with symbols][7]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![GDB output with symbols][5]
|
||||
|
||||
The additional debugging information will increase the size of the executable. In this case, it increases the executable by 2.5 times (from 26,088 byte to 65,480 byte).
|
||||
|
||||
Start the program with the `-c1` switch by typing `run -c1`. The program will start and crash when it reaches `State_4`:
|
||||
Start the program with the `-c1` switch by typing `run -c1`. The program will start and crash when it reaches `State_4` :
|
||||
|
||||
![gdb output crash on c1 switch][8]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![gdb output crash on c1 switch][6]
|
||||
|
||||
You can retrieve additional information about the program. The command `info source` provides information about the current file:
|
||||
|
||||
![gdb info source output][9]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
* 101 lines
|
||||
* Language: C++
|
||||
* Compiler (version, tuning, architecture, debug flag, language standard)
|
||||
* Debugging format: [DWARF 2][10]
|
||||
* No preprocessor macro information available (when compiled with GCC, macros are available only when [compiled with the `-g3` flag][11]).
|
||||
![gdb info source output][7]
|
||||
|
||||
|
||||
* 101 lines
|
||||
* Language: C++
|
||||
* Compiler (version, tuning, architecture, debug flag, language standard)
|
||||
* Debugging format: [DWARF 2][8]
|
||||
* No preprocessor macro information available (when compiled with GCC, macros are available only when [compiled with the `-g3` flag][9]).
|
||||
|
||||
The command `info shared` prints a list of dynamic libraries with their addresses in the virtual address space that was loaded on startup so that the program will execute:
|
||||
|
||||
![gdb info shared output][12]
|
||||
![gdb info shared output][10]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
If you want to learn about library handling in Linux, see my article [_How to handle dynamic and static libraries in Linux_][13].
|
||||
If you want to learn about library handling in Linux, see my article [How to handle dynamic and static libraries in Linux][11].
|
||||
|
||||
### Debug the program
|
||||
|
||||
You may have noticed that you can start the program inside GDB with the `run` command. The `run` command accepts command-line arguments like you would use to start the program from the console. The `-c1` switch will cause the program to crash on stage 4. To run the program from the beginning, you don't have to quit GDB; simply use the `run` command again. Without the `-c1` switch, the program executes an infinite loop. You would have to stop it with **Ctrl+C**.
|
||||
|
||||
![gdb output stopped by sigint][14]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![gdb output stopped by sigint][12]
|
||||
|
||||
You can also execute a program step by step. In C/C++, the entry point is the `main` function. Use the command `list main` to open the part of the source code that shows the `main` function:
|
||||
|
||||
![gdb output list main][15]
|
||||
![gdb output list main][13]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
The `main` function is on line 33, so add a breakpoint there by typing `break 33` :
|
||||
|
||||
The `main` function is on line 33, so add a breakpoint there by typing `break 33`:
|
||||
|
||||
![gdb output breakpoint added][16]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![gdb output breakpoint added][14]
|
||||
|
||||
Run the program by typing `run`. As expected, the program stops at the `main` function. Type `layout src` to show the source code in parallel:
|
||||
|
||||
![gdb output break at main][17]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![gdb output break at main][15]
|
||||
|
||||
You are now in GDB's text user interface (TUI) mode. Use the Up and Down arrow keys to scroll through the source code.
|
||||
|
||||
@ -126,13 +106,11 @@ GDB highlights the line to be executed. By typing `next` (n), you can execute th
|
||||
|
||||
From time to time, you will notice that TUI's output gets a bit corrupted:
|
||||
|
||||
![gdb output corrupted][18]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![gdb output corrupted][16]
|
||||
|
||||
If this happens, press **Ctrl+L** to reset the screen.
|
||||
|
||||
Use **Ctrl+X+A** to enter and leave TUI mode at will. You can find [other key bindings][19] in the manual.
|
||||
Use **Ctrl+X+A** to enter and leave TUI mode at will. You can find [other key bindings][17] in the manual.
|
||||
|
||||
To quit GDB, simply type `quit`.
|
||||
|
||||
@ -140,45 +118,39 @@ To quit GDB, simply type `quit`.
|
||||
|
||||
The heart of this example program consists of a state machine running in an infinite loop. The variable `n_state` is a simple enum that determines the current state:
|
||||
|
||||
|
||||
```
|
||||
while(true){
|
||||
switch(n_state){
|
||||
case State_1:
|
||||
std::cout << "State_1 reached" << std::flush;
|
||||
std::cout << "State_1 reached" << std::flush;
|
||||
n_state = State_2;
|
||||
break;
|
||||
case State_2:
|
||||
std::cout << "State_2 reached" << std::flush;
|
||||
std::cout << "State_2 reached" << std::flush;
|
||||
n_state = State_3;
|
||||
break;
|
||||
|
||||
|
||||
(.....)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You want to stop the program when `n_state` is set to the value `State_5`. To do so, stop the program at the `main` function and set a watchpoint for `n_state`:
|
||||
|
||||
You want to stop the program when `n_state` is set to the value `State_5`. To do so, stop the program at the `main` function and set a watchpoint for `n_state` :
|
||||
|
||||
```
|
||||
`watch n_state == State_5`
|
||||
watch n_state == State_5
|
||||
```
|
||||
|
||||
Setting watchpoints with the variable name works only if the desired variable is available in the current context.
|
||||
|
||||
When you continue the program's execution by typing `continue`, you should get output like:
|
||||
|
||||
![gdb output stop on watchpoint_1][20]
|
||||
![gdb output stop on watchpoint_1][18]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
If you continue the execution, GDB will stop when the watchpoint expression evaluates to `false` :
|
||||
|
||||
If you continue the execution, GDB will stop when the watchpoint expression evaluates to `false`:
|
||||
|
||||
![gdb output stop on watchpoint_2][21]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![gdb output stop on watchpoint_2][19]
|
||||
|
||||
You can specify watchpoints for general value changes, specific values, and read or write access.
|
||||
|
||||
@ -186,21 +158,17 @@ You can specify watchpoints for general value changes, specific values, and read
|
||||
|
||||
Type `info watchpoints` to print a list of previously set watchpoints:
|
||||
|
||||
![gdb output info watchpoints][22]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![gdb output info watchpoints][20]
|
||||
|
||||
#### Delete breakpoints and watchpoints
|
||||
|
||||
As you can see, watchpoints are numbers. To delete a specific watchpoint, type `delete` followed by the number of the watchpoint. For example, my watchpoint has the number 2; to remove this watchpoint, enter `delete 2`.
|
||||
|
||||
_Caution:_ If you use `delete` without specifying a number, _all_ watchpoints and breakpoints will be deleted.
|
||||
*Caution:* If you use `delete` without specifying a number, *all* watchpoints and breakpoints will be deleted.
|
||||
|
||||
The same applies to breakpoints. In the screenshot below, I added several breakpoints and printed a list of them by typing `info breakpoint`:
|
||||
The same applies to breakpoints. In the screenshot below, I added several breakpoints and printed a list of them by typing `info breakpoint` :
|
||||
|
||||
![gdb output info breakpoints][23]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![gdb output info breakpoints][21]
|
||||
|
||||
To remove a single breakpoint, type `delete` followed by its number. Alternatively, you can remove a breakpoint by specifying its line number. For example, the command `clear 78` will remove breakpoint number 7, which is set on line 78.
|
||||
|
||||
@ -208,9 +176,7 @@ To remove a single breakpoint, type `delete` followed by its number. Alternative
|
||||
|
||||
Instead of removing a breakpoint or watchpoint, you can disable it by typing `disable` followed by its number. In the following, breakpoints 3 and 4 are disabled and are marked with a minus sign in the code window:
|
||||
|
||||
![disabled breakpoints][24]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![disabled breakpoints][22]
|
||||
|
||||
It is also possible to modify a range of breakpoints or watchpoints by typing something like `disable 2 - 4`. If you want to reactivate the points, type `enable` followed by their numbers.
|
||||
|
||||
@ -224,40 +190,31 @@ The `main` function includes the variable `n_state_3_count`, which is incremente
|
||||
|
||||
To add a conditional breakpoint based on the value of `n_state_3_count` type:
|
||||
|
||||
|
||||
```
|
||||
`break 54 if n_state_3_count == 3`
|
||||
break 54 if n_state_3_count == 3
|
||||
```
|
||||
|
||||
![Set conditional breakpoint][25]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![Set conditional breakpoint][23]
|
||||
|
||||
Continue the execution. The program will execute the state machine three times before it stops at line 54. To check the value of `n_state_3_count`, type:
|
||||
|
||||
|
||||
```
|
||||
`print n_state_3_count`
|
||||
print n_state_3_count
|
||||
```
|
||||
|
||||
![print variable][26]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![print variable][24]
|
||||
|
||||
#### Make breakpoints conditional
|
||||
|
||||
It is also possible to make an existing breakpoint conditional. Remove the recently added breakpoint with `clear 54`, and add a simple breakpoint by typing `break 54`. You can make this breakpoint conditional by typing:
|
||||
|
||||
|
||||
```
|
||||
`condition 3 n_state_3_count == 9`
|
||||
condition 3 n_state_3_count == 9
|
||||
```
|
||||
|
||||
The `3` refers to the breakpoint number.
|
||||
|
||||
![modify breakpoint][27]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![modify breakpoint][25]
|
||||
|
||||
#### Set breakpoints in other source files
|
||||
|
||||
@ -269,143 +226,109 @@ In addition to breakpoints and watchpoints, you can also set catchpoints. Catchp
|
||||
|
||||
To catch the `write` syscall, which is used to write to STDOUT, enter:
|
||||
|
||||
|
||||
```
|
||||
`catch syscall write`
|
||||
catch syscall write
|
||||
```
|
||||
|
||||
![catch syscall write output][28]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![catch syscall write output][26]
|
||||
|
||||
Each time the program writes to the console output, GDB will interrupt execution.
|
||||
|
||||
In the manual, you can find a whole chapter [covering break-, watch-, and catchpoints][29].
|
||||
In the manual, you can find a whole chapter [covering break-, watch-, and catchpoints][27].
|
||||
|
||||
### Evaluate and manipulate symbols
|
||||
|
||||
Printing the values of variables is done with the `print` command. The general syntax is `print <expression> <value>`. The value of a variable can be modified by typing:
|
||||
|
||||
|
||||
```
|
||||
`set variable <variable-name> <new-value>.`
|
||||
set variable <variable-name> <new-value>.
|
||||
```
|
||||
|
||||
In the screenshot below, I gave the variable `n_state_3_count` the value _123_.
|
||||
In the screenshot below, I gave the variable `n_state_3_count` the value *123*.
|
||||
|
||||
![print variable][30]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![catch syscall write output][28]
|
||||
|
||||
The `/x` expression prints the value in hexadecimal; with the `&` operator, you can print the address within the virtual address space.
|
||||
|
||||
If you are not sure of a certain symbol's data type, you can find it with `whatis`:
|
||||
If you are not sure of a certain symbol's data type, you can find it with `whatis` :
|
||||
|
||||
![whatis output][31]
|
||||
![whatis output][29]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
If you want to list all variables that are available in the scope of the `main` function, type `info scope main` :
|
||||
|
||||
If you want to list all variables that are available in the scope of the `main` function, type `info scope main`:
|
||||
|
||||
![info scope main output][32]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![info scope main output][30]
|
||||
|
||||
The `DW_OP_fbreg` values refer to the stack offset based on the current subroutine.
|
||||
|
||||
Alternatively, if you are already inside a function and want to list all variables on the current stack frame, you can use `info locals`:
|
||||
Alternatively, if you are already inside a function and want to list all variables on the current stack frame, you can use `info locals` :
|
||||
|
||||
![info locals output][33]
|
||||
![info locals output][31]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
Check the manual to learn more about [examining symbols][34].
|
||||
Check the manual to learn more about [examining symbols][32].
|
||||
|
||||
### Attach to a running process
|
||||
|
||||
The command `gdb attach <process-id>` allows you to attach to an already running process by specifying the process ID (PID). Luckily, the `coredump` program prints its current PID to the screen, so you don't have to manually find it with [ps][35] or [top][36].
|
||||
The command `gdb attach <process-id>` allows you to attach to an already running process by specifying the process ID (PID). Luckily, the `coredump` program prints its current PID to the screen, so you don't have to manually find it with [ps][33] or [top][34].
|
||||
|
||||
Start an instance of the coredump application:
|
||||
|
||||
|
||||
```
|
||||
`./coredump`
|
||||
./coredump
|
||||
```
|
||||
|
||||
![coredump application][37]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![coredump application][35]
|
||||
|
||||
The operating system gives the PID `2849`. Open a separate console window, move to the coredump application's source directory, and attach GDB:
|
||||
|
||||
|
||||
```
|
||||
`gdb attach 2849`
|
||||
gdb attach 2849
|
||||
```
|
||||
|
||||
![attach GDB to coredump][38]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![attach GDB to coredump][36]
|
||||
|
||||
GDB immediately stops the execution when you attach it. Type `layout src` and `backtrace` to examine the call stack:
|
||||
|
||||
![layout src and backtrace output][39]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![layout src and backtrace output][37]
|
||||
|
||||
The output shows the process interrupted while executing the `std::this_thread::sleep_for<...>(...)` function that was called in line 92 of `main.cpp`.
|
||||
|
||||
As soon as you quit GDB, the process will continue running.
|
||||
|
||||
You can find more information about [attaching to a running process][40] in the GDB manual.
|
||||
You can find more information about [attaching to a running process][38] in the GDB manual.
|
||||
|
||||
#### Move through the stack
|
||||
|
||||
Return to the program by using `up` two times to move up in the stack to `main.cpp`:
|
||||
Return to the program by using `up` two times to move up in the stack to `main.cpp` :
|
||||
|
||||
![moving up the stack to main.cpp][41]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
![moving up the stack to main.cpp][39]
|
||||
|
||||
Usually, the compiler will create a subroutine for each function or method. Each subroutine has its own stack frame, so moving upwards in the stackframe means moving upwards in the callstack.
|
||||
|
||||
You can find out more about [stack evaluation][42] in the manual.
|
||||
You can find out more about [stack evaluation][40] in the manual.
|
||||
|
||||
#### Specify the source files
|
||||
|
||||
When attaching to an already running process, GDB will look for the source files in the current working directory. Alternatively, you can specify the source directories manually with the [`directory` command][43].
|
||||
When attaching to an already running process, GDB will look for the source files in the current working directory. Alternatively, you can specify the source directories manually with the [directory command][41].
|
||||
|
||||
### Evaluate dump files
|
||||
|
||||
Read [_Creating and debugging Linux dump files_][3] for information about this topic.
|
||||
Read [Creating and debugging Linux dump files][42] for information about this topic.
|
||||
|
||||
TL;DR:
|
||||
|
||||
1. I assume you're working with a recent version of Fedora
|
||||
|
||||
2. Invoke coredump with the c1 switch: `coredump -c1`
|
||||
1. I assume you're working with a recent version of Fedora
|
||||
2. Invoke coredump with the c1 switch: `coredump -c1`
|
||||
3. Load the latest dumpfile with GDB: `coredumpctl debug`
|
||||
4. Open TUI mode and enter `layout src`
|
||||
|
||||
![Crash meme][44]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
3. Load the latest dumpfile with GDB: `coredumpctl debug`
|
||||
|
||||
4. Open TUI mode and enter `layout src`
|
||||
|
||||
|
||||
|
||||
|
||||
![coredump output][45]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
The output of `backtrace` shows that the crash happened five stack frames away from `main.cpp`. Enter to jump directly to the faulty line of code in `main.cpp`:
|
||||
The output of `backtrace` shows that the crash happened five stack frames away from `main.cpp`. Enter to jump directly to the faulty line of code in `main.cpp` :
|
||||
|
||||
![up 5 output][46]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
A look at the source code shows that the program tried to free a pointer that was not returned by a memory management function. This results in undefined behavior and caused the `SIGABRT`.
|
||||
|
||||
### Debug without symbols
|
||||
@ -416,56 +339,43 @@ Check out how it works with this example.
|
||||
|
||||
Go to the source directory, open the **Makefile**, and edit line 9 like this:
|
||||
|
||||
|
||||
```
|
||||
`CFLAGS =-Wall -Werror -std=c++11 #-g`
|
||||
CFLAGS =-Wall -Werror -std=c++11 #-g
|
||||
```
|
||||
|
||||
To recompile the program, run `make clean` followed by `make` and start GDB. The program no longer has any debugging symbols to lead the way through the source code.
|
||||
To recompile the program, run `make clean` followed by `make` and start GDB. The program no longer has any debugging symbols to lead the way through the source code.
|
||||
|
||||
![no debugging symbols][48]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
The command `info file` reveals the memory areas and entry point of the binary:
|
||||
|
||||
![info file output][49]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
The entry point corresponds with the beginning of the `.text` area, which contains the actual opcode. To add a breakpoint at the entry point, type `break *0x401110` then start execution by typing `run`:
|
||||
The entry point corresponds with the beginning of the `.text` area, which contains the actual opcode. To add a breakpoint at the entry point, type `break *0x401110` then start execution by typing `run` :
|
||||
|
||||
![breakpoint at the entry point][50]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
To set up a breakpoint at a certain address, specify it with the dereferencing operator `*`.
|
||||
|
||||
#### Choose the disassembler flavor
|
||||
|
||||
Before digging deeper into assembly, you can choose which [assembly flavor][51] to use. GDB's default is AT&T, but I prefer the Intel syntax. Change it with:
|
||||
|
||||
Before digging deeper into assembly, you can choose which [assembly flavor][51] to use. GDB's default is AT&T, but I prefer the Intel syntax. Change it with:
|
||||
|
||||
```
|
||||
`set disassembly-flavor intel`
|
||||
set disassembly-flavor intel
|
||||
```
|
||||
|
||||
![changing assembly flavor][52]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
Now open the assembly and register the window by typing `layout asm` and `layout reg`. You should now see output like this:
|
||||
|
||||
![layout asm and layout reg output][53]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
#### Save configuration files
|
||||
|
||||
Although you have already entered many commands, you haven't actually started debugging. If you are heavily debugging an application or trying to solve a reverse-engineering challenge, it can be useful to save your GDB-specific settings in a file.
|
||||
|
||||
The [config file `gdbinit`][54] in this project's GitHub repository contains the recently used commands:
|
||||
|
||||
The [config file gdbinit][54] in this project's GitHub repository contains the recently used commands:
|
||||
|
||||
```
|
||||
set disassembly-flavor intel
|
||||
@ -486,11 +396,9 @@ With the `c2` switch applied, the program will crash. The program stops at the e
|
||||
|
||||
![continuing execution after crash][55]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
The `idiv` instruction performs an integer division with the dividend in the `RAX` register and the divisor specified as an argument. The quotient is loaded into the `RAX` register, and the remainder is loaded into `RDX`.
|
||||
|
||||
From the register overview, you can see the `RAX` contains _5_, so you have to find out which value is stored on the stack at position `RBP-0x4`.
|
||||
From the register overview, you can see the `RAX` contains *5*, so you have to find out which value is stored on the stack at position `RBP-0x4`.
|
||||
|
||||
#### Read memory
|
||||
|
||||
@ -498,71 +406,58 @@ To read raw memory content, you must specify a few more parameters than for read
|
||||
|
||||
![stack division output][56]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
You're most interested in the value of `rbp-0x4` because this is the position where the argument for `idiv` is stored. From the screenshot, you can see that the next variable is located at `rbp-0x8`, so the variable at `rbp-0x4` is 4 bytes wide.
|
||||
|
||||
In GDB, you can use the `x` command to _examine_ any memory content:
|
||||
In GDB, you can use the `x` command to *examine* any memory content:
|
||||
|
||||
> `x/` < optional parameter `n` `f` `u` > < memory address `addr` >
|
||||
> `x/` < optional parameter `n` `f` `u` > < memory address `addr` >
|
||||
|
||||
Optional parameters:
|
||||
|
||||
* `n`: Repeat count (default: 1) refers to the unit size
|
||||
* `f`: Format specifier, like in [printf][57]
|
||||
* `u`: Unit size
|
||||
* `b`: bytes
|
||||
* `h`: half words (2 bytes)
|
||||
* `w`: word (4 bytes)(default)
|
||||
* `g`: giant word (8 bytes)
|
||||
* n: Repeat count (default: 1) refers to the unit size
|
||||
* f: Format specifier, like in [printf][57]
|
||||
* u: Unit size
|
||||
* b: `b`ytes
|
||||
h: `h`alf `w`ords (2 bytes)
|
||||
w: word (4 bytes)(default)
|
||||
* g: `g`iant word (8 bytes)
|
||||
|
||||
|
||||
|
||||
To print out the value at `rbp-0x4`, type `x/u $rbp-4`:
|
||||
To print out the value at `rbp-0x4`, type `x/u $rbp-4` :
|
||||
|
||||
![print value][58]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
If you keep this pattern in mind, it's straightforward to examine the memory. Check the [examining memory][59] section in the manual.
|
||||
|
||||
#### Manipulate the assembly
|
||||
|
||||
The arithmetic exception happened in the subroutine `zeroDivide()`. When you scroll a bit upward with the Up arrow key, you can find this pattern:
|
||||
|
||||
|
||||
```
|
||||
0x401211 <_Z10zeroDividev> push rbp
|
||||
0x401212 <_Z10zeroDividev+1> mov rbp,rsp
|
||||
0x401211 <_Z10zeroDividev> push rbp
|
||||
0x401212 <_Z10zeroDividev+1> mov rbp,rsp
|
||||
```
|
||||
|
||||
This is called the [function prologue][60]:
|
||||
|
||||
1. The base pointer (`rbp`) of the calling function is stored on the stack
|
||||
2. The value of the stack pointer (`rsp`) is loaded to the base pointer (`rbp`)
|
||||
1. The base pointer (rbp) of the calling function is stored on the stack
|
||||
2. The value of the stack pointer (rsp) is loaded to the base pointer (rbp)
|
||||
|
||||
|
||||
|
||||
Skip this subroutine completely. You can check the call stack with `backtrace`. You are only one stack frame ahead of your `main` function, so you can go back to `main` with a single `up`:
|
||||
Skip this subroutine completely. You can check the call stack with `backtrace`. You are only one stack frame ahead of your `main` function, so you can go back to `main` with a single `up` :
|
||||
|
||||
![Callstack assembly][61]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
In your `main` function, you can find this pattern:
|
||||
|
||||
|
||||
```
|
||||
0x401431 <main+497> cmp BYTE PTR [rbp-0x12],0x0
|
||||
0x401435 <main+501> je 0x40145f <main+543>
|
||||
0x401437 <main+503> call 0x401211<_Z10zeroDividev>
|
||||
0x401431 <main+497> cmp BYTE PTR [rbp-0x12],0x0
|
||||
0x401435 <main+501> je 0x40145f <main+543>
|
||||
0x401437 <main+503> call 0x401211<_Z10zeroDividev>
|
||||
```
|
||||
|
||||
The subroutine `zeroDivide()` is entered only when `jump equal (je)` evaluates to `true`. You can easily replace this with a `jump-not-equal (jne)` instruction, which has the opcode `0x75` (provided you are on an x86/64 architecture; the opcodes are different on other architectures). Restart the program by typing `run`. When the program stops at the entry function, manipulate the opcode by typing:
|
||||
|
||||
|
||||
```
|
||||
`set *(unsigned char*)0x401435 = 0x75`
|
||||
set *(unsigned char*)0x401435 = 0x75
|
||||
```
|
||||
|
||||
Finally, type `continue`. The program will skip the subroutine `zeroDivide()` and won't crash anymore.
|
||||
@ -573,8 +468,6 @@ You can find GDB working in the background in many integrated development enviro
|
||||
|
||||
![GDB in VSCodium][63]
|
||||
|
||||
(Stephan Avenwedde, [CC BY-SA 4.0][5])
|
||||
|
||||
It's useful to know how to leverage GDB's functionality. Usually, not all of GDB's functions can be used from the IDE, so you benefit from having experience using GDB from the command line.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@ -582,74 +475,74 @@ It's useful to know how to leverage GDB's functionality. Usually, not all of GDB
|
||||
via: https://opensource.com/article/21/1/gnu-project-debugger
|
||||
|
||||
作者:[Stephan Avenwedde][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/hansic99
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/mistake_bug_fix_find_error.png?itok=PZaz3dga (magnifying glass on computer screen, finding a bug in the code)
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://opensource.com/sites/default/files/lead-images/mistake_bug_fix_find_error.png
|
||||
[2]: https://www.gnu.org/software/gdb/
|
||||
[3]: https://opensource.com/article/20/8/linux-dump
|
||||
[4]: https://opensource.com/sites/default/files/uploads/gdb_output_no_dbg_symbols.png (gdb coredump output)
|
||||
[5]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[6]: tmp.2p0XrqmAS5#without_symbols
|
||||
[7]: https://opensource.com/sites/default/files/uploads/gdb_output_with_symbols.png (GDB output with symbols)
|
||||
[8]: https://opensource.com/sites/default/files/uploads/gdb_output_crash_on_c1_switch.png (gdb output crash on c1 switch)
|
||||
[9]: https://opensource.com/sites/default/files/uploads/gdb_output_info_source.png (gdb info source output)
|
||||
[10]: http://dwarfstd.org/
|
||||
[11]: https://sourceware.org/gdb/current/onlinedocs/gdb/Compilation.html#Compilation
|
||||
[12]: https://opensource.com/sites/default/files/uploads/gdb_output_info_shared.png (gdb info shared output)
|
||||
[13]: https://opensource.com/article/20/6/linux-libraries
|
||||
[14]: https://opensource.com/sites/default/files/uploads/gdb_output_stopped_by_sigint.png (gdb output stopped by sigint)
|
||||
[15]: https://opensource.com/sites/default/files/uploads/gdb_output_list_main.png (gdb output list main)
|
||||
[16]: https://opensource.com/sites/default/files/uploads/gdb_output_breakpoint_added.png (gdb output breakpoint added)
|
||||
[17]: https://opensource.com/sites/default/files/uploads/gdb_output_break_at_main.png (gdb output break at main)
|
||||
[18]: https://opensource.com/sites/default/files/images/gdb_output_screen_corrupted.png (gdb output corrupted)
|
||||
[19]: https://sourceware.org/gdb/onlinedocs/gdb/TUI-Keys.html#TUI-Keys
|
||||
[20]: https://opensource.com/sites/default/files/uploads/gdb_output_stop_on_watchpoint_1.png (gdb output stop on watchpoint_1)
|
||||
[21]: https://opensource.com/sites/default/files/uploads/gdb_output_stop_on_watchpoint_2.png (gdb output stop on watchpoint_2)
|
||||
[22]: https://opensource.com/sites/default/files/uploads/gdb_output_info_watchpoints.png (gdb output info watchpoints)
|
||||
[23]: https://opensource.com/sites/default/files/uploads/gdb_output_info_breakpoints.png (gdb output info breakpoints)
|
||||
[24]: https://opensource.com/sites/default/files/uploads/gdb_output_disabled_breakpoints.png (disabled breakpoints)
|
||||
[25]: https://opensource.com/sites/default/files/uploads/gdb_output_set_conditional_breakpoint.png (Set conditional breakpoint)
|
||||
[26]: https://opensource.com/sites/default/files/uploads/gdb_output_print_variable.png (print variable)
|
||||
[27]: https://opensource.com/sites/default/files/uploads/gdb_output_modify_breakpoint.png (modify breakpoint)
|
||||
[28]: https://opensource.com/sites/default/files/uploads/gdb_output_syscall_catch.png (catch syscall write output)
|
||||
[29]: https://sourceware.org/gdb/current/onlinedocs/gdb/Breakpoints.html#Breakpoints
|
||||
[30]: https://opensource.com/sites/default/files/uploads/gdb_output_print_and_modify.png (print variable)
|
||||
[31]: https://opensource.com/sites/default/files/uploads/gdb_output_whatis.png (whatis output)
|
||||
[32]: https://opensource.com/sites/default/files/uploads/gdb_output_info_scope_main.png (info scope main output)
|
||||
[33]: https://opensource.com/sites/default/files/uploads/gdb_output_info_locals_main.png (info locals output)
|
||||
[34]: https://sourceware.org/gdb/current/onlinedocs/gdb/Symbols.html
|
||||
[35]: https://man7.org/linux/man-pages/man1/ps.1.html
|
||||
[36]: https://man7.org/linux/man-pages/man1/top.1.html
|
||||
[37]: https://opensource.com/sites/default/files/uploads/coredump_running.png (coredump application)
|
||||
[38]: https://opensource.com/sites/default/files/uploads/gdb_output_attaching_to_process.png (attach GDB to coredump)
|
||||
[39]: https://opensource.com/sites/default/files/uploads/gdb_output_backtrace.png (layout src and backtrace output)
|
||||
[40]: https://sourceware.org/gdb/current/onlinedocs/gdb/Attach.html#Attach
|
||||
[41]: https://opensource.com/sites/default/files/uploads/gdb_output_stackframe_up.png (moving up the stack to main.cpp)
|
||||
[42]: https://sourceware.org/gdb/current/onlinedocs/gdb/Stack.html#Stack
|
||||
[43]: https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_48.html#SEC49
|
||||
[44]: https://opensource.com/sites/default/files/uploads/crash.png (Crash meme)
|
||||
[45]: https://opensource.com/sites/default/files/uploads/gdb_output_coredump.png (coredump output)
|
||||
[46]: https://opensource.com/sites/default/files/uploads/gdb_output_up_five.png (up 5 output)
|
||||
[4]: https://opensource.com/sites/default/files/uploads/gdb_output_no_dbg_symbols.png
|
||||
[5]: https://opensource.com/sites/default/files/uploads/gdb_output_with_symbols.png
|
||||
[6]: https://opensource.com/sites/default/files/uploads/gdb_output_crash_on_c1_switch.png
|
||||
[7]: https://opensource.com/sites/default/files/uploads/gdb_output_info_source.png
|
||||
[8]: http://dwarfstd.org/
|
||||
[9]: https://sourceware.org/gdb/current/onlinedocs/gdb/Compilation.html#Compilation
|
||||
[10]: https://opensource.com/sites/default/files/uploads/gdb_output_info_shared.png
|
||||
[11]: https://opensource.com/article/20/6/linux-libraries
|
||||
[12]: https://opensource.com/sites/default/files/uploads/gdb_output_stopped_by_sigint.png
|
||||
[13]: https://opensource.com/sites/default/files/uploads/gdb_output_list_main.png
|
||||
[14]: https://opensource.com/sites/default/files/uploads/gdb_output_breakpoint_added.png
|
||||
[15]: https://opensource.com/sites/default/files/uploads/gdb_output_break_at_main.png
|
||||
[16]: https://opensource.com/sites/default/files/images/gdb_output_screen_corrupted.png
|
||||
[17]: https://sourceware.org/gdb/onlinedocs/gdb/TUI-Keys.html#TUI-Keys
|
||||
[18]: https://opensource.com/sites/default/files/uploads/gdb_output_stop_on_watchpoint_1.png
|
||||
[19]: https://opensource.com/sites/default/files/uploads/gdb_output_stop_on_watchpoint_2.png
|
||||
[20]: https://opensource.com/sites/default/files/uploads/gdb_output_info_watchpoints.png
|
||||
[21]: https://opensource.com/sites/default/files/uploads/gdb_output_info_breakpoints.png
|
||||
[22]: https://opensource.com/sites/default/files/uploads/gdb_output_disabled_breakpoints.png
|
||||
[23]: https://opensource.com/sites/default/files/uploads/gdb_output_set_conditional_breakpoint.png
|
||||
[24]: https://opensource.com/sites/default/files/uploads/gdb_output_print_variable.png
|
||||
[25]: https://opensource.com/sites/default/files/uploads/gdb_output_modify_breakpoint.png
|
||||
[26]: https://opensource.com/sites/default/files/uploads/gdb_output_syscall_catch.png
|
||||
[27]: https://sourceware.org/gdb/current/onlinedocs/gdb/Breakpoints.html#Breakpoints
|
||||
[28]: https://opensource.com/sites/default/files/uploads/gdb_output_print_and_modify.png
|
||||
[29]: https://opensource.com/sites/default/files/uploads/gdb_output_whatis.png
|
||||
[30]: https://opensource.com/sites/default/files/uploads/gdb_output_info_scope_main.png
|
||||
[31]: https://opensource.com/sites/default/files/uploads/gdb_output_info_locals_main.png
|
||||
[32]: https://sourceware.org/gdb/current/onlinedocs/gdb/Symbols.html
|
||||
[33]: https://man7.org/linux/man-pages/man1/ps.1.html
|
||||
[34]: https://man7.org/linux/man-pages/man1/top.1.html
|
||||
[35]: https://opensource.com/sites/default/files/uploads/coredump_running.png
|
||||
[36]: https://opensource.com/sites/default/files/uploads/gdb_output_attaching_to_process.png
|
||||
[37]: https://opensource.com/sites/default/files/uploads/gdb_output_backtrace.png
|
||||
[38]: https://sourceware.org/gdb/current/onlinedocs/gdb/Attach.html#Attach
|
||||
[39]: https://opensource.com/sites/default/files/uploads/gdb_output_stackframe_up.png
|
||||
[40]: https://sourceware.org/gdb/current/onlinedocs/gdb/Stack.html#Stack
|
||||
[41]: https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_48.html#SEC49
|
||||
[42]: https://opensource.com/article/20/8/linux-dump
|
||||
[43]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[44]: https://opensource.com/sites/default/files/uploads/crash.png
|
||||
[45]: https://opensource.com/sites/default/files/uploads/gdb_output_coredump.png
|
||||
[46]: https://opensource.com/sites/default/files/uploads/gdb_output_up_five.png
|
||||
[47]: https://en.wikipedia.org/wiki/Assembly_language
|
||||
[48]: https://opensource.com/sites/default/files/uploads/gdb_output_no_debugging_symbols.png (no debugging symbols)
|
||||
[49]: https://opensource.com/sites/default/files/uploads/gdb_output_info_file.png (info file output)
|
||||
[50]: https://opensource.com/sites/default/files/uploads/gdb_output_break_at_start.png (breakpoint at the entry point)
|
||||
[48]: https://opensource.com/sites/default/files/uploads/gdb_output_no_debugging_symbols.png
|
||||
[49]: https://opensource.com/sites/default/files/uploads/gdb_output_info_file.png
|
||||
[50]: https://opensource.com/sites/default/files/uploads/gdb_output_break_at_start.png
|
||||
[51]: https://en.wikipedia.org/wiki/X86_assembly_language#Syntax
|
||||
[52]: https://opensource.com/sites/default/files/uploads/gdb_output_disassembly_flavor.png (changing assembly flavor)
|
||||
[53]: https://opensource.com/sites/default/files/uploads/gdb_output_layout_reg_asm.png (layout asm and layout reg output)
|
||||
[52]: https://opensource.com/sites/default/files/uploads/gdb_output_disassembly_flavor.png
|
||||
[53]: https://opensource.com/sites/default/files/uploads/gdb_output_layout_reg_asm.png
|
||||
[54]: https://github.com/hANSIc99/core_dump_example/blob/master/gdbinit
|
||||
[55]: https://opensource.com/sites/default/files/uploads/gdb_output_asm_div_zero.png (continuing execution after crash)
|
||||
[56]: https://opensource.com/sites/default/files/uploads/gdb_output_stack_division.png (stack division output)
|
||||
[55]: https://opensource.com/sites/default/files/uploads/gdb_output_asm_div_zero.png
|
||||
[56]: https://opensource.com/sites/default/files/uploads/gdb_output_stack_division.png
|
||||
[57]: https://en.wikipedia.org/wiki/Printf_format_string#Type_field
|
||||
[58]: https://opensource.com/sites/default/files/uploads/gdb_output_examine_1.png (print value)
|
||||
[58]: https://opensource.com/sites/default/files/uploads/gdb_output_examine_1.png
|
||||
[59]: https://sourceware.org/gdb/current/onlinedocs/gdb/Memory.html
|
||||
[60]: https://en.wikipedia.org/wiki/Function_prologue
|
||||
[61]: https://opensource.com/sites/default/files/uploads/gdb_output_callstack_assembly_0.png (Callstack assembly)
|
||||
[61]: https://opensource.com/sites/default/files/uploads/gdb_output_callstack_assembly_0.png
|
||||
[62]: https://github.com/WebFreak001/code-debug
|
||||
[63]: https://opensource.com/sites/default/files/uploads/vs_codium_native_debug.png (GDB in VSCodium)
|
||||
[63]: https://opensource.com/sites/default/files/uploads/vs_codium_native_debug.png
|
||||
|
@ -1,18 +1,20 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Analyze Kubernetes files for errors with KubeLinter)
|
||||
[#]: via: (https://opensource.com/article/21/1/kubelinter)
|
||||
[#]: author: (Jessica Cherry https://opensource.com/users/cherrybomb)
|
||||
[#]: subject: "Analyze Kubernetes files for errors with KubeLinter"
|
||||
[#]: via: "https://opensource.com/article/21/1/kubelinter"
|
||||
[#]: author: "Jessica Cherry https://opensource.com/users/cherrybomb"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
Analyze Kubernetes files for errors with KubeLinter
|
||||
======
|
||||
Find and fix errors in your Helm charts and Kubernetes configuration
|
||||
files with KubeLinter.
|
||||
Find and fix errors in your Helm charts and Kubernetes configuration files with KubeLinter.
|
||||
|
||||
![magnifying glass on computer screen, finding a bug in the code][1]
|
||||
|
||||
Image by: Opensource.com
|
||||
|
||||
[KubeLinter][2] is an open source project released by Stackrox to analyze Kubernetes YAML files for security issues and errant code. The tool covers Helm charts and Kubernetes configuration files, including [Knative][3] files. Using it can improve cloud-native development, reduce development time, and encourage DevOps best practices.
|
||||
|
||||
### Download and install
|
||||
@ -23,23 +25,20 @@ You have several options to install KubeLinter.
|
||||
|
||||
You can install manually from the Git repository:
|
||||
|
||||
|
||||
```
|
||||
$ git clone [git@github.com][4]:stackrox/kube-linter.git
|
||||
$ cd kube-linter && make build
|
||||
$ git clone git@github.com:stackrox/kube-linter.git
|
||||
$ cd kube-linter && make build
|
||||
$ .gobin/kube-linter version
|
||||
```
|
||||
|
||||
If you use [Homebrew][5], you can install it with the `brew` command:
|
||||
|
||||
If you use [Homebrew][4], you can install it with the `brew` command:
|
||||
|
||||
```
|
||||
`$ brew install kube-linter`
|
||||
$ brew install kube-linter
|
||||
```
|
||||
|
||||
You can also install it with Go (as I did):
|
||||
|
||||
|
||||
```
|
||||
$ GO111MODULE=on go get golang.stackrox.io/kube-linter/cmd/kube-linter
|
||||
go: finding golang.stackrox.io/kube-linter latest
|
||||
@ -48,11 +47,10 @@ go: extracting golang.stackrox.io/kube-linter v0.0.0-20201204022312-475075c74675
|
||||
[...]
|
||||
```
|
||||
|
||||
After installing, you must make an alias in your `~/.bashrc`:
|
||||
|
||||
After installing, you must make an alias in your `~/.bashrc` :
|
||||
|
||||
```
|
||||
$ echo "alias kube-linter=$HOME/go/bin/kube-linter" >> ~/.bashrc
|
||||
$ echo "alias kube-linter=$HOME/go/bin/kube-linter" >> ~/.bashrc
|
||||
$ source ~/.bashrc
|
||||
```
|
||||
|
||||
@ -60,7 +58,6 @@ $ source ~/.bashrc
|
||||
|
||||
Now that the tool is installed, try it out on a Helm chart. First, start Minikube with a clean build and some small configuration changes:
|
||||
|
||||
|
||||
```
|
||||
$ minikube config set kubernetes-version v1.19.0
|
||||
$ minikube config set memory 8000
|
||||
@ -68,23 +65,22 @@ $ minikube config set memory 8000
|
||||
$ minikube config set cpus 12
|
||||
❗ These changes will take effect upon a minikube delete and then a minikube start
|
||||
$ minikube delete
|
||||
🔥 Deleting "minikube" in docker ...
|
||||
🔥 Deleting container "minikube" ...
|
||||
🔥 Removing /home/jess/.minikube/machines/minikube ...
|
||||
💀 Removed all traces of the "minikube" cluster.
|
||||
? Deleting "minikube" in docker ...
|
||||
? Deleting container "minikube" ...
|
||||
? Removing /home/jess/.minikube/machines/minikube ...
|
||||
? Removed all traces of the "minikube" cluster.
|
||||
|
||||
$ minikube start
|
||||
😄 minikube v1.14.2 on Debian bullseye/sid
|
||||
? minikube v1.14.2 on Debian bullseye/sid
|
||||
✨ Using the docker driver based on user configuration
|
||||
👍 Starting control plane node minikube in cluster minikube
|
||||
🎉 minikube 1.15.1 is available! Download it: <https://github.com/kubernetes/minikube/releases/tag/v1.15.1>
|
||||
💡 To disable this notice, run: 'minikube config set WantUpdateNotification false'
|
||||
? Starting control plane node minikube in cluster minikube
|
||||
? minikube 1.15.1 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.15.1
|
||||
? To disable this notice, run: 'minikube config set WantUpdateNotification false'
|
||||
|
||||
💾 Downloading Kubernetes v1.19.0 preload ...
|
||||
? Downloading Kubernetes v1.19.0 preload ...
|
||||
```
|
||||
|
||||
Once everything is running, create an example Helm chart called `first_test`:
|
||||
|
||||
Once everything is running, create an example Helm chart called `first_test` :
|
||||
|
||||
```
|
||||
$ helm create first_test
|
||||
@ -95,7 +91,6 @@ first_test
|
||||
|
||||
Test KubeLinter against the new, unedited chart. Run the `kube-linter` command to see the available commands and flags:
|
||||
|
||||
|
||||
```
|
||||
$ kube-linter
|
||||
Usage:
|
||||
@ -103,8 +98,8 @@ Usage:
|
||||
|
||||
Available Commands:
|
||||
checks View more information on lint checks
|
||||
help Help about any command
|
||||
lint Lint Kubernetes YAML files and Helm charts
|
||||
help Help about any command
|
||||
lint Lint Kubernetes YAML files and Helm charts
|
||||
templates View more information on check templates
|
||||
version Print version and exit
|
||||
|
||||
@ -116,13 +111,12 @@ Use "/home/jess/go/bin/kube-linter [command] --help" for more information about
|
||||
|
||||
Then test what the basic `lint` command does to your example chart. You'll end up with many errors, so I'll grab a snippet of some issues:
|
||||
|
||||
|
||||
```
|
||||
$ kube-linter lint first_test/
|
||||
|
||||
first_test/first_test/templates/deployment.yaml: (object: <no namespace>/test-release-first_test apps/v1, Kind=Deployment) container "first_test" does not have a read-only root file system (check: no-read-only-root-fs, remediation: Set readOnlyRootFilesystem to true in your container's securityContext.)
|
||||
first_test/first_test/templates/deployment.yaml: (object: <no namespace>/test-release-first_test apps/v1, Kind=Deployment) container "first_test" does not have a read-only root file system (check: no-read-only-root-fs, remediation: Set readOnlyRootFilesystem to true in your container's securityContext.)
|
||||
|
||||
first_test/first_test/templates/deployment.yaml: (object: <no namespace>/test-release-first_test apps/v1, Kind=Deployment) container "first_test" is not set to runAsNonRoot (check: run-as-non-root, remediation: Set runAsUser to a non-zero number, and runAsNonRoot to true, in your pod or container securityContext. See <https://kubernetes.io/docs/tasks/configure-pod-container/security-context/> for more details.)
|
||||
first_test/first_test/templates/deployment.yaml: (object: <no namespace>/test-release-first_test apps/v1, Kind=Deployment) container "first_test" is not set to runAsNonRoot (check: run-as-non-root, remediation: Set runAsUser to a non-zero number, and runAsNonRoot to true, in your pod or container securityContext. See https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for more details.)
|
||||
[...]
|
||||
Error: found 12 lint errors
|
||||
```
|
||||
@ -131,14 +125,12 @@ For the sake of brevity, I picked two security issues that are easy for me to f
|
||||
|
||||
The `kube-linter` output provides hints about the required fixes. For instance, the first error ends with:
|
||||
|
||||
|
||||
```
|
||||
`remediation: Set readOnlyRootFilesystem to true in your container's securityContext.`
|
||||
remediation: Set readOnlyRootFilesystem to true in your container's securityContext.
|
||||
```
|
||||
|
||||
The next step is clear: Open the `values.yaml` file in a text editor (I use Vi, but you can use whatever you like) and locate the `securityContext` section:
|
||||
|
||||
|
||||
```
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
@ -151,12 +143,11 @@ securityContext: {}
|
||||
|
||||
Uncomment the section and remove the braces:
|
||||
|
||||
|
||||
```
|
||||
securityContext:
|
||||
securityContext:
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
@ -164,20 +155,18 @@ securityContext:
|
||||
|
||||
Save the file and rerun the linter. Those errors no longer show up in the list, and the error count changes.
|
||||
|
||||
|
||||
```
|
||||
`Error: found 10 lint errors`
|
||||
Error: found 10 lint errors
|
||||
```
|
||||
|
||||
Congratulations! You have resolved security issues!
|
||||
|
||||
### Kubernetes with KubeLinter
|
||||
|
||||
This example uses an app file from my [previous article on Knative][6] to test against Kubernetes config files. I already have Knative up and running, so you may want to review that article if it's not running on your system.
|
||||
This example uses an app file from my [previous article on Knative][5] to test against Kubernetes config files. I already have Knative up and running, so you may want to review that article if it's not running on your system.
|
||||
|
||||
I downloaded the Kourier service YAML file for this example:
|
||||
|
||||
|
||||
```
|
||||
$ ls
|
||||
kourier.yaml first_test
|
||||
@ -185,13 +174,12 @@ kourier.yaml first_test
|
||||
|
||||
Start by running the linter against `kourier.yaml`. Again, there are several issues. I'll focus on resource problems:
|
||||
|
||||
|
||||
```
|
||||
$ kube-linter lint kourier.yaml
|
||||
$ kube-linter lint kourier.yaml
|
||||
|
||||
kourier.yaml: (object: kourier-system/3scale-kourier-gateway apps/v1, Kind=Deployment) container "kourier-gateway" has cpu limit 0 (check: unset-cpu-requirements, remediation: Set your container's CPU requests and limits depending on its requirements. See <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\#requests-and-limits> for more details.)
|
||||
kourier.yaml: (object: kourier-system/3scale-kourier-gateway apps/v1, Kind=Deployment) container "kourier-gateway" has cpu limit 0 (check: unset-cpu-requirements, remediation: Set your container's CPU requests and limits depending on its requirements. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for more details.)
|
||||
|
||||
kourier.yaml: (object: kourier-system/3scale-kourier-gateway apps/v1, Kind=Deployment) container "kourier-gateway" has memory request 0 (check: unset-memory-requirements, remediation: Set your container's memory requests and limits depending on its requirements. See <https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/\#requests-and-limits> for more details.)
|
||||
kourier.yaml: (object: kourier-system/3scale-kourier-gateway apps/v1, Kind=Deployment) container "kourier-gateway" has memory request 0 (check: unset-memory-requirements, remediation: Set your container's memory requests and limits depending on its requirements. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits for more details.)
|
||||
|
||||
Error: found 12 lint errors
|
||||
```
|
||||
@ -200,7 +188,6 @@ Since this is a single deployment file, you can edit it directly. Open it in a t
|
||||
|
||||
Start with deployment:
|
||||
|
||||
|
||||
```
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@ -214,70 +201,67 @@ metadata:
|
||||
|
||||
The containers section has some problems:
|
||||
|
||||
|
||||
```
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --base-id 1
|
||||
- -c /tmp/config/envoy-bootstrap.yaml
|
||||
- --log-level info
|
||||
command:
|
||||
- /usr/local/bin/envoy
|
||||
image: docker.io/maistra/proxyv2-ubi8:1.1.5
|
||||
imagePullPolicy: Always
|
||||
name: kourier-gateway
|
||||
ports:
|
||||
- name: http2-external
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: http2-internal
|
||||
containerPort: 8081
|
||||
protocol: TCP
|
||||
- name: https-external
|
||||
containerPort: 8443
|
||||
protocol: TCP
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --base-id 1
|
||||
- -c /tmp/config/envoy-bootstrap.yaml
|
||||
- --log-level info
|
||||
command:
|
||||
- /usr/local/bin/envoy
|
||||
image: docker.io/maistra/proxyv2-ubi8:1.1.5
|
||||
imagePullPolicy: Always
|
||||
name: kourier-gateway
|
||||
ports:
|
||||
- name: http2-external
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: http2-internal
|
||||
containerPort: 8081
|
||||
protocol: TCP
|
||||
- name: https-external
|
||||
containerPort: 8443
|
||||
protocol: TCP
|
||||
```
|
||||
|
||||
Add some specs to the container configuration:
|
||||
|
||||
|
||||
```
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --base-id 1
|
||||
- -c /tmp/config/envoy-bootstrap.yaml
|
||||
- --log-level info
|
||||
command:
|
||||
- /usr/local/bin/envoy
|
||||
image: docker.io/maistra/proxyv2-ubi8:1.1.5
|
||||
imagePullPolicy: Always
|
||||
name: kourier-gateway
|
||||
ports:
|
||||
- name: http2-external
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: http2-internal
|
||||
containerPort: 8081
|
||||
protocol: TCP
|
||||
- name: https-external
|
||||
containerPort: 8443
|
||||
protocol: TCP
|
||||
spec:
|
||||
containers:
|
||||
- args:
|
||||
- --base-id 1
|
||||
- -c /tmp/config/envoy-bootstrap.yaml
|
||||
- --log-level info
|
||||
command:
|
||||
- /usr/local/bin/envoy
|
||||
image: docker.io/maistra/proxyv2-ubi8:1.1.5
|
||||
imagePullPolicy: Always
|
||||
name: kourier-gateway
|
||||
ports:
|
||||
- name: http2-external
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
- name: http2-internal
|
||||
containerPort: 8081
|
||||
protocol: TCP
|
||||
- name: https-external
|
||||
containerPort: 8443
|
||||
protocol: TCP
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
```
|
||||
|
||||
When you rerun the linter, you'll notice these issues no longer show in the output, and the error count changes:
|
||||
|
||||
|
||||
```
|
||||
`Error: found 8 lint errors`
|
||||
Error: found 8 lint errors
|
||||
```
|
||||
|
||||
Congratulations! You have fixed resource issues in your Kubernetes file!
|
||||
@ -293,17 +277,16 @@ I think KubeLinter's best part is that each error message includes documentation
|
||||
via: https://opensource.com/article/21/1/kubelinter
|
||||
|
||||
作者:[Jessica Cherry][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/cherrybomb
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/mistake_bug_fix_find_error.png?itok=PZaz3dga (magnifying glass on computer screen, finding a bug in the code)
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://opensource.com/sites/default/files/lead-images/mistake_bug_fix_find_error.png
|
||||
[2]: https://github.com/stackrox/kube-linter
|
||||
[3]: https://knative.dev/
|
||||
[4]: mailto:git@github.com
|
||||
[5]: https://opensource.com/article/20/6/homebrew-linux
|
||||
[6]: https://opensource.com/article/20/11/knative
|
||||
[4]: https://opensource.com/article/20/6/homebrew-linux
|
||||
[5]: https://opensource.com/article/20/11/knative
|
||||
|
@ -1,40 +1,40 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (How I programmed a virtual gift exchange)
|
||||
[#]: via: (https://opensource.com/article/21/1/open-source-gift-exchange)
|
||||
[#]: author: (Chris Hermansen https://opensource.com/users/clhermansen)
|
||||
[#]: subject: "How I programmed a virtual gift exchange"
|
||||
[#]: via: "https://opensource.com/article/21/1/open-source-gift-exchange"
|
||||
[#]: author: "Chris Hermansen https://opensource.com/users/clhermansen"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
How I programmed a virtual gift exchange
|
||||
======
|
||||
A book club takes its annual gift exchange online with the help of HTML,
|
||||
CSS, and JavaScript.
|
||||
A book club takes its annual gift exchange online with the help of HTML, CSS, and JavaScript.
|
||||
|
||||
![Package wrapped with brown paper and red bow][1]
|
||||
|
||||
Image by: Photo by [Jess Bailey][2] on [Unsplash][3]
|
||||
|
||||
Every year, my wife's book club has a book exchange during the holidays. Due to the need to maintain physical distance in 2020, I created an online gift exchange for them to use during a book club videoconference. Apparently, the virtual book exchange worked out (at least, I received kind compliments from the book club members), so I decided to share this simple little hack.
|
||||
|
||||
### How the book exchange usually works
|
||||
|
||||
In past years, the exchange has gone something like this:
|
||||
|
||||
1. Each person buys a book and wraps it up.
|
||||
2. Everyone arrives at the host's home and puts the wrapped books in a pile.
|
||||
3. Each person draws a number out of a hat, which establishes their turn.
|
||||
4. The person who drew No. 1 selects a book from the pile and unwraps it. In turn, each subsequent person chooses to either take a wrapped book from the pile or to steal an unwrapped book from someone who has gone before.
|
||||
5. When someone's book is stolen, they can either replace it with a wrapped book from the pile or steal another book (but not the one that was stolen from them) from someone else.
|
||||
6. And so on… eventually, someone has to take the last unwrapped book to end the exchange.
|
||||
|
||||
|
||||
1. Each person buys a book and wraps it up.
|
||||
2. Everyone arrives at the host's home and puts the wrapped books in a pile.
|
||||
3. Each person draws a number out of a hat, which establishes their turn.
|
||||
4. The person who drew No. 1 selects a book from the pile and unwraps it. In turn, each subsequent person chooses to either take a wrapped book from the pile or to steal an unwrapped book from someone who has gone before.
|
||||
5. When someone's book is stolen, they can either replace it with a wrapped book from the pile or steal another book (but not the one that was stolen from them) from someone else.
|
||||
6. And so on… eventually, someone has to take the last unwrapped book to end the exchange.
|
||||
|
||||
### Designing the virtual book exchange
|
||||
|
||||
My first decision was which implementation platform to use for the book exchange. Because there would already be a browser open to host the videoconference, I decided to use HTML, CSS, and JavaScript.
|
||||
|
||||
Then it was design time. After some thinking, I decided to use rectangles to represent the book club members and the books. The books would be draggable, and when one was dropped on a member's rectangle, the book would unwrap (and stay unwrapped). I needed some "wrapping paper," so I used this source of [free-to-use images][2].
|
||||
Then it was design time. After some thinking, I decided to use rectangles to represent the book club members and the books. The books would be draggable, and when one was dropped on a member's rectangle, the book would unwrap (and stay unwrapped). I needed some "wrapping paper," so I used this source of [free-to-use images][4].
|
||||
|
||||
I took screenshots of the patterns I liked and used [GIMP][3] to scale the images to the right width and height.
|
||||
I took screenshots of the patterns I liked and used [GIMP][5] to scale the images to the right width and height.
|
||||
|
||||
I needed a way to handle draggable and droppable interactions; given that I've been using jQuery and jQuery UI for several years now, I decided to continue along that path.
|
||||
|
||||
@ -42,25 +42,19 @@ For a while, I struggled with what a droppable element should do when something
|
||||
|
||||
Jumping to the results, here's a screenshot of the user interface at the beginning of the exchange:
|
||||
|
||||
![Virtual book exchange][4]
|
||||
|
||||
(Chris Hermansen, [CC BY-SA 4.0][5])
|
||||
![Virtual book exchange][6]
|
||||
|
||||
There are nine book club members: Wanda, Carlos, Bill, and so on. There are also nine fairly ugly wrapped parcels.
|
||||
|
||||
Let's say Wanda goes first and chooses the flower wrapping paper. The host clicks and drags that parcel to Wanda's name, and the parcel unwraps:
|
||||
|
||||
![Virtual book exchange][6]
|
||||
|
||||
(Chris Hermansen, [CC BY-SA 4.0][5])
|
||||
![Virtual book exchange][7]
|
||||
|
||||
Whoops! That title and author are a bit too long to fit on the book's "cover." Oh well, I'll fix that in the next version.
|
||||
|
||||
Carlos is next. He decides he really wants to read that book, so he steals it. Wanda then chooses the paisley pattern, and the screen looks like this:
|
||||
|
||||
![Virtual book exchange][7]
|
||||
|
||||
(Chris Hermansen, [CC BY-SA 4.0][5])
|
||||
![Virtual book exchange][8]
|
||||
|
||||
And so on until the exchange ends.
|
||||
|
||||
@ -68,120 +62,117 @@ And so on until the exchange ends.
|
||||
|
||||
So what about the code? Here it is:
|
||||
|
||||
|
||||
```
|
||||
1 <!doctype html>
|
||||
2 <[html][8] lang="en">
|
||||
3 <[head][9]>
|
||||
4 <[meta][10] charset="utf-8">
|
||||
5 <[title][11]>Book Exchange</[title][11]>
|
||||
6 <[link][12] rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css">
|
||||
7 <[style][13]>
|
||||
8 .draggable {
|
||||
9 float: left;
|
||||
10 width: 90px;
|
||||
11 height: 90px;
|
||||
12 background: #ccc;
|
||||
13 padding: 5px;
|
||||
14 margin: 5px 5px 5px 0;
|
||||
15 }
|
||||
16 .droppable {
|
||||
17 float: left;
|
||||
18 width: 100px;
|
||||
19 height: 125px;
|
||||
20 background: #999;
|
||||
21 color: #fff;
|
||||
22 padding: 10px;
|
||||
23 margin: 10px 10px 10px 0;
|
||||
24 }
|
||||
25 </[style][13]>
|
||||
26 <[script][14] src="[https://code.jquery.com/jquery-1.12.4.js"\>\][15]</[script][14]>
|
||||
27 <[script][14] src="[https://code.jquery.com/ui/1.12.1/jquery-ui.js"\>\][16]</[script][14]>
|
||||
28 </[head][9]>
|
||||
29 <[body][17]>
|
||||
30 <[h1][18] style="color:#1a1aff;">Raffles Book Club Remote Gift Exchange</[h1][18]>
|
||||
31 <[h2][19] style="color:#aa0a0a;">The players, in random order, and the luxurious gifts, wrapped:</[h2][19]>
|
||||
32
|
||||
33 <[div][20]>
|
||||
34 <[div][20] id="wanda" class="droppable">Wanda</[div][20]>
|
||||
35 <[div][20] id="carlos" class="droppable">Carlos</[div][20]>
|
||||
36 <[div][20] id="bill" class="droppable">Bill</[div][20]>
|
||||
37 <[div][20] id="arlette" class="droppable">Arlette</[div][20]>
|
||||
38 <[div][20] id="joanne" class="droppable">Joanne</[div][20]>
|
||||
39 <[div][20] id="aleks" class="droppable">Alekx</[div][20]>
|
||||
40 <[div][20] id="ermintrude" class="droppable">Ermintrude</[div][20]>
|
||||
41 <[div][20] id="walter" class="droppable">Walter</[div][20]>
|
||||
42 <[div][20] id="hilary" class="droppable">Hilary</[div][20]>
|
||||
43 </[div][20]>
|
||||
44 <[div][20]>
|
||||
45 <[div][20] id="bows" class="draggable" style="background-image: url('bows.png');"></[div][20]>
|
||||
46 <[div][20] id="boxes" class="draggable" style="background-image: url('boxes.png');"></[div][20]>
|
||||
47 <[div][20] id="circles" class="draggable" style="background-image: url('circles.png');"></[div][20]>
|
||||
48 <[div][20] id="gerbers" class="draggable" style="background-image: url('gerbers.png');"></[div][20]>
|
||||
49 <[div][20] id="hippie" class="draggable" style="background-image: url('hippie.png');"></[div][20]>
|
||||
50 <[div][20] id="lattice" class="draggable" style="background-image: url('lattice.png');"></[div][20]>
|
||||
51 <[div][20] id="nautical" class="draggable" style="background-image: url('nautical.png');"></[div][20]>
|
||||
52 <[div][20] id="splodges" class="draggable" style="background-image: url('splodges.png');"></[div][20]>
|
||||
53 <[div][20] id="ugly" class="draggable" style="background-image: url('ugly.png');"></[div][20]>
|
||||
54 </[div][20]>
|
||||
55
|
||||
56 <[script][14]>
|
||||
57 var books = {
|
||||
58 'bows': 'Untamed by Glennon Doyle',
|
||||
59 'boxes': "The Heart's Invisible Furies by John Boyne",
|
||||
60 'circles': 'The Great Halifax Explosion by John Bacon',
|
||||
61 'gerbers': 'Homes: A Refugee Story by Abu Bakr al Rabeeah, Winnie Yeung',
|
||||
62 'hippie': 'Before We Were Yours by Lisa Wingate',
|
||||
63 'lattice': "Hamnet and Judith by Maggie O'Farrell",
|
||||
64 'nautical': 'Shuggy Bain by Douglas Stewart',
|
||||
65 'splodges': 'Magdalena by Wade Davis',
|
||||
66 'ugly': 'Funny Boy by Shyam Selvadurai'
|
||||
67 };
|
||||
68 $( ".droppable" ).droppable({
|
||||
69 drop: function(event, ui) {
|
||||
70 var element = $(ui.draggable[0]);
|
||||
71 var wrapping = element.attr('id');
|
||||
72 /* alert( $(this).text() + " got " + wrapping); */
|
||||
73 $(ui.draggable[0]).css("background-image","url(book_cover.png)");
|
||||
74 $(ui.draggable[0]).text(books[wrapping]);
|
||||
75 },
|
||||
76 out: function() {
|
||||
77 /* alert( $(this).text() + " lost it" ); */
|
||||
78 }
|
||||
79 });
|
||||
80 $( ".draggable" ).draggable();
|
||||
81 </[script][14]>
|
||||
82
|
||||
83 </[body][17]>
|
||||
84 </[html][8]>
|
||||
1 <!doctype html>
|
||||
2 <html lang="en">
|
||||
3 <head>
|
||||
4 <meta charset="utf-8">
|
||||
5 <title>Book Exchange</title>
|
||||
6 <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css">
|
||||
7 <style>
|
||||
8 .draggable {
|
||||
9 float: left;
|
||||
10 width: 90px;
|
||||
11 height: 90px;
|
||||
12 background: #ccc;
|
||||
13 padding: 5px;
|
||||
14 margin: 5px 5px 5px 0;
|
||||
15 }
|
||||
16 .droppable {
|
||||
17 float: left;
|
||||
18 width: 100px;
|
||||
19 height: 125px;
|
||||
20 background: #999;
|
||||
21 color: #fff;
|
||||
22 padding: 10px;
|
||||
23 margin: 10px 10px 10px 0;
|
||||
24 }
|
||||
25 </style>
|
||||
26 <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
|
||||
27 <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
||||
28 </head>
|
||||
29 <body>
|
||||
30 <h1 style="color:#1a1aff;">Raffles Book Club Remote Gift Exchange</h1>
|
||||
31 <h2 style="color:#aa0a0a;">The players, in random order, and the luxurious gifts, wrapped:</h2>
|
||||
32
|
||||
33 <div>
|
||||
34 <div id="wanda" class="droppable">Wanda</div>
|
||||
35 <div id="carlos" class="droppable">Carlos</div>
|
||||
36 <div id="bill" class="droppable">Bill</div>
|
||||
37 <div id="arlette" class="droppable">Arlette</div>
|
||||
38 <div id="joanne" class="droppable">Joanne</div>
|
||||
39 <div id="aleks" class="droppable">Alekx</div>
|
||||
40 <div id="ermintrude" class="droppable">Ermintrude</div>
|
||||
41 <div id="walter" class="droppable">Walter</div>
|
||||
42 <div id="hilary" class="droppable">Hilary</div>
|
||||
43 </div>
|
||||
44 <div>
|
||||
45 <div id="bows" class="draggable" style="background-image: url('bows.png');"></div>
|
||||
46 <div id="boxes" class="draggable" style="background-image: url('boxes.png');"></div>
|
||||
47 <div id="circles" class="draggable" style="background-image: url('circles.png');"></div>
|
||||
48 <div id="gerbers" class="draggable" style="background-image: url('gerbers.png');"></div>
|
||||
49 <div id="hippie" class="draggable" style="background-image: url('hippie.png');"></div>
|
||||
50 <div id="lattice" class="draggable" style="background-image: url('lattice.png');"></div>
|
||||
51 <div id="nautical" class="draggable" style="background-image: url('nautical.png');"></div>
|
||||
52 <div id="splodges" class="draggable" style="background-image: url('splodges.png');"></div>
|
||||
53 <div id="ugly" class="draggable" style="background-image: url('ugly.png');"></div>
|
||||
54 </div>
|
||||
55
|
||||
56 <script>
|
||||
57 var books = {
|
||||
58 'bows': 'Untamed by Glennon Doyle',
|
||||
59 'boxes': "The Heart's Invisible Furies by John Boyne",
|
||||
60 'circles': 'The Great Halifax Explosion by John Bacon',
|
||||
61 'gerbers': 'Homes: A Refugee Story by Abu Bakr al Rabeeah, Winnie Yeung',
|
||||
62 'hippie': 'Before We Were Yours by Lisa Wingate',
|
||||
63 'lattice': "Hamnet and Judith by Maggie O'Farrell",
|
||||
64 'nautical': 'Shuggy Bain by Douglas Stewart',
|
||||
65 'splodges': 'Magdalena by Wade Davis',
|
||||
66 'ugly': 'Funny Boy by Shyam Selvadurai'
|
||||
67 };
|
||||
68 $( ".droppable" ).droppable({
|
||||
69 drop: function(event, ui) {
|
||||
70 var element = $(ui.draggable[0]);
|
||||
71 var wrapping = element.attr('id');
|
||||
72 /* alert( $(this).text() + " got " + wrapping); */
|
||||
73 $(ui.draggable[0]).css("background-image","url(book_cover.png)");
|
||||
74 $(ui.draggable[0]).text(books[wrapping]);
|
||||
75 },
|
||||
76 out: function() {
|
||||
77 /* alert( $(this).text() + " lost it" ); */
|
||||
78 }
|
||||
79 });
|
||||
80 $( ".draggable" ).draggable();
|
||||
81 </script>
|
||||
82
|
||||
83 </body>
|
||||
84 </html>
|
||||
```
|
||||
|
||||
### Breaking it down
|
||||
|
||||
Let's go over this code bit by bit.
|
||||
|
||||
* **Lines 1–6:** Upfront, I have the usual HTML boilerplate, `HTML`, `HEAD`, `META`, `TITLE` elements, followed by a link to the CSS for jQuery UI.
|
||||
* **Lines 7–25:** I added two new style classes: `draggable` and `droppable`. These define the layout for the books (draggable) and the people (droppable). Note that, aside from defining the size, background color, padding, and margin, I established that these need to float left. This way, the layout adjusts to the browser window width in a reasonably acceptable form.
|
||||
* **Line 26–27:** With the CSS out of the way, it's time for the JavaScript libraries, first jQuery, then jQuery UI.
|
||||
* **Lines 29–83:** Now that the `HEAD` element is done, next is the `BODY`:
|
||||
* **Lines 30–31:** These couple of titles, `H1` and `H2`, let people know what they're doing here.
|
||||
* **Lines 33–43:** A `DIV` to contain the people:
|
||||
* **Lines 34–42:** The people are defined as droppable `DIV` elements and given `ID` fields corresponding to their names.
|
||||
* **Lines 44–54:** A `DIV` to contain the books:
|
||||
* **Lines 45–53:** The books are defined as draggable `DIV` elements. Each element is declared with a background image corresponding to the wrapping paper with no text between the `<div>` and `</div>`. The `ID` fields correspond to the wrapping paper.
|
||||
* **Lines 56–81:** These contain JavaScript to make it all work.
|
||||
* **Lines 57–67:** This JavaScript object contains the book definitions. The keys (`'bows'`, `'boxes'`, etc.) correspond to the `ID` fields of the book `DIV` elements. The values (`'Untamed by Glennon Doyle',` `"The Heart's Invisible Furies by John Boyne"`, etc.) are the book titles and authors.
|
||||
* **Lines 68–79:** This JavaScript jQuery UI function defines the droppable functionality to be attached to HTML elements whose class is `droppable`.
|
||||
* **Lines 69–75:** When a `draggable` element is dropped onto a `droppable` element, the function `drop` is called.
|
||||
* **Line 70:** The `element` variable is assigned the draggable object that was dropped (this will be a `<div id="..." class="draggable"...></div>` element.
|
||||
* **Line 71:** The `wrapping` variable is assigned the value of the `ID` field in the draggable object.
|
||||
* **Line 72:** This line is commented out, but while I was learning and testing, calls to `alert()` were useful.
|
||||
* **Line 73:** This reassigns the draggable object's background image to a bland image on which text can be read; part 1 of unwrapping is getting rid of the wrapping paper.
|
||||
* **Line 74:** This sets the text of the draggable object to the title of the book, looked up in the book's object using the draggable object's ID; part 2 of the unwrapping is showing the book title and author.
|
||||
* **Lines 76–78:** For a while, I thought I wanted something to happen when a draggable object was removed from a droppable object (e.g., when a club member stole a book), which would require using the `out` function in a droppable object. Eventually, I decided not to do anything. But, this could note that the book was stolen and make it "unstealable" for one turn; or it could show a status line that says something like: _"Wanda's book Blah Blah by Joe Blogs was stolen, and she needs to choose another."_
|
||||
* **Line 80:** This JavaScript jQuery UI function defines the draggable functionality to be attached to HTML elements whose class is `draggable`. In my case, the default behavior was all I needed.
|
||||
|
||||
|
||||
* Lines 1–6: Upfront, I have the usual `HTML` boilerplate, HTML, `HEAD`, `META`, `TITLE` elements, followed by a link to the CSS for jQuery UI.
|
||||
* Lines 7–25: I added two new style classes: `draggable` and `droppable`. These define the layout for the books (draggable) and the people (droppable). Note that, aside from defining the size, background color, padding, and margin, I established that these need to float left. This way, the layout adjusts to the browser window width in a reasonably acceptable form.
|
||||
* Line 26–27: With the CSS out of the way, it's time for the JavaScript libraries, first jQuery, then jQuery UI.
|
||||
* Lines 29–83: Now that the `HEAD` `element` is done, next is the `BODY`:
|
||||
* Lines 30–31: These couple of titles, `H1` and `H2`, let people know what they're doing here.
|
||||
Lines 33–43: A `DIV` to contain the people:
|
||||
Lines 34–42: The people are defined as `droppable` `DIV` elements and given `ID` fields corresponding to their names.
|
||||
Lines 44–54: A `DIV` to contain the books:
|
||||
Lines 45–53: The books are defined as `draggable` `DIV` elements. Each element is declared with a background image corresponding to the `wrapping` paper with no text between the `<div>` and `</div>`. The `ID` fields correspond to the wrapping paper.
|
||||
Lines 56–81: These contain JavaScript to make it all work.
|
||||
* Lines 57–67: This JavaScript object contains the book definitions. The keys ('bows', `'boxes'`, etc.) correspond to the `ID` fields of the book `DIV` elements. The values ('Untamed by Glennon Doyle', `"The Heart's Invisible Furies by John Boyne"`, etc.) are the book titles and authors.
|
||||
Lines 68–79: This JavaScript jQuery UI function defines the `droppable` functionality to be attached to HTML elements whose class is `drop`pable.
|
||||
Lines 69–75: When a `draggable` element is dropped onto a droppable element, the function drop is called.
|
||||
Line 70: The element variable is assigned the draggable object that was dropped (this will be a `<div id="..." class="draggable"...></div>` element.
|
||||
Line 71: The wrapping variable is assigned the value of the `ID` field in the draggable object.
|
||||
Line 72: This line is commented `out`, but while I was learning and testing, calls to `alert()` were useful.
|
||||
* Line 73: This reassigns the draggable object's background image to a bland image on which text can be read; part 1 of unwrapping is getting rid of the wrapping paper.
|
||||
* Line 74: This sets the text of the draggable object to the title of the book, looked up in the book's object using the draggable object's ID; part 2 of the unwrapping is showing the book title and author.
|
||||
Lines 76–78: For a while, I thought I wanted something to happen when a draggable object was removed from a droppable object (e.g., when a club member stole a book), which would require using the out function in a droppable object. Eventually, I decided not to do anything. But, this could note that the book was stolen and make it "unstealable" for one turn; or it could show a status line that says something like: "Wanda's book Blah Blah by Joe Blogs was stolen, and she needs to choose another."
|
||||
Line 80: This JavaScript jQuery UI function defines the draggable functionality to be attached to HTML elements whose class is draggable. In my case, the default behavior was all I needed.
|
||||
|
||||
That's it!
|
||||
|
||||
@ -189,49 +180,38 @@ That's it!
|
||||
|
||||
Libraries like jQuery and jQuery UI are incredibly helpful when trying to do something complicated in JavaScript. Look at the `$().draggable()` and `$().droppable()` functions, for example:
|
||||
|
||||
|
||||
```
|
||||
`$( ".draggable" ).draggable();`
|
||||
$( ".draggable" ).draggable();
|
||||
```
|
||||
|
||||
The `".draggable"` allows associating the `draggable()` function with any HTML element whose class is "draggable." The `draggable()` function comes with all sorts of useful behavior about picking, dragging, and releasing a draggable HTML element.
|
||||
|
||||
If you haven't spent much time with jQuery, I really like the book [_jQuery in Action_][21] by Bear Bibeault, Yehuda Katz, and Aurelio De Rosa. Similarly, [_jQuery UI in Action_][22] by TJ VanToll is a great help with the jQuery UI (where draggable and droppable come from).
|
||||
If you haven't spent much time with jQuery, I really like the book [jQuery in Action][9] by Bear Bibeault, Yehuda Katz, and Aurelio De Rosa. Similarly, [jQuery UI in Action][10] by TJ VanToll is a great help with the jQuery UI (where draggable and droppable come from).
|
||||
|
||||
Of course, there are many other JavaScript libraries, frameworks, and what-nots around to do good stuff in the user interface. I haven't really started to explore all that jQuery and jQuery UI offer, and I want to play around with the rest to see what can be done.
|
||||
|
||||
Image by: (Chris Hermansen, CC BY-SA 4.0)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/1/open-source-gift-exchange
|
||||
|
||||
作者:[Chris Hermansen][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/clhermansen
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/brown-package-red-bow.jpg?itok=oxZYQzH- (Package wrapped with brown paper and red bow)
|
||||
[2]: https://all-free-download.com/free-vector/patterns-creative-commons.html#google_vignette
|
||||
[3]: https://opensource.com/tags/gimp
|
||||
[4]: https://opensource.com/sites/default/files/uploads/bookexchangestart.png (Virtual book exchange)
|
||||
[5]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[6]: https://opensource.com/sites/default/files/uploads/bookexchangeperson1.png (Virtual book exchange)
|
||||
[7]: https://opensource.com/sites/default/files/uploads/bookexchangeperson2.png (Virtual book exchange)
|
||||
[8]: http://december.com/html/4/element/html.html
|
||||
[9]: http://december.com/html/4/element/head.html
|
||||
[10]: http://december.com/html/4/element/meta.html
|
||||
[11]: http://december.com/html/4/element/title.html
|
||||
[12]: http://december.com/html/4/element/link.html
|
||||
[13]: http://december.com/html/4/element/style.html
|
||||
[14]: http://december.com/html/4/element/script.html
|
||||
[15]: https://code.jquery.com/jquery-1.12.4.js"\>\
|
||||
[16]: https://code.jquery.com/ui/1.12.1/jquery-ui.js"\>\
|
||||
[17]: http://december.com/html/4/element/body.html
|
||||
[18]: http://december.com/html/4/element/h1.html
|
||||
[19]: http://december.com/html/4/element/h2.html
|
||||
[20]: http://december.com/html/4/element/div.html
|
||||
[21]: https://www.manning.com/books/jquery-in-action-third-edition
|
||||
[22]: https://www.manning.com/books/jquery-ui-in-action
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://opensource.com/sites/default/files/lead-images/brown-package-red-bow.jpg
|
||||
[2]: https://unsplash.com/@jessbaileydesigns?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
|
||||
[3]: https://unsplash.com/s/photos/package?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText
|
||||
[4]: https://all-free-download.com/free-vector/patterns-creative-commons.html#google_vignette
|
||||
[5]: https://opensource.com/tags/gimp
|
||||
[6]: https://opensource.com/sites/default/files/uploads/bookexchangestart.png
|
||||
[7]: https://opensource.com/sites/default/files/uploads/bookexchangeperson1.png
|
||||
[8]: https://opensource.com/sites/default/files/uploads/bookexchangeperson2.png
|
||||
[9]: https://www.manning.com/books/jquery-in-action-third-edition
|
||||
[10]: https://www.manning.com/books/jquery-ui-in-action
|
||||
|
@ -1,164 +1,151 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Introduction to Thunderbird mail filters)
|
||||
[#]: via: (https://fedoramagazine.org/introduction-to-thunderbird-mail-filters/)
|
||||
[#]: author: (Richard England https://fedoramagazine.org/author/rlengland/)
|
||||
[#]: subject: "Introduction to Thunderbird mail filters"
|
||||
[#]: via: "https://fedoramagazine.org/introduction-to-thunderbird-mail-filters/"
|
||||
[#]: author: "Richard England https://fedoramagazine.org/author/rlengland/"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
Introduction to Thunderbird mail filters
|
||||
======
|
||||
|
||||
![][1]
|
||||
|
||||
Everyone eventually runs into an inbox loaded with messages that they need to sort through. If you are like a lot of people, this is not a fast process. However, use of mail filters can make the task a little less tedious by letting Thunderbird pre-sort the messages into categories that reflect their source, priority, or usefulness. This article is an introduction to the creation of filters in Thunderbird.
|
||||
|
||||
Filters may be created for each email account you have created in Thunderbird. These are the accounts you see in the main Thunderbird folder pane shown at the left of the “Classic Layout”.
|
||||
|
||||
![Classic Layout][2]
|
||||
![][2]
|
||||
|
||||
There are two methods that can be used to create mail filters for your accounts. The first is based on the currently selected account and the second on the currently selected message. Both are discussed here.
|
||||
|
||||
### Message destination folder
|
||||
|
||||
Before filtering messages there has to be a destination for them. Create the destination by selecting a location to create a new folder. In this example the destination will be **Local Folders** shown in the accounts pane. Right click on **Local Folders** and select _New Folder…_ from the menu.
|
||||
Before filtering messages there has to be a destination for them. Create the destination by selecting a location to create a new folder. In this example the destination will be **Local Folders**shown in the accounts pane. Right click on **Local Folders** and select *New Folder…* from the menu.
|
||||
|
||||
![Creating a new folder][3]
|
||||
![][3]
|
||||
|
||||
Enter the name of the new folder in the menu and select _Create Folder._ The mail to filter is coming from the New York Times so that is the name entered.
|
||||
Enter the name of the new folder in the menu and select *Create Folder.* The mail to filter is coming from the New York Times so that is the name entered.
|
||||
|
||||
![Folder creation][4]
|
||||
![][4]
|
||||
|
||||
### Filter creation based on the selected account
|
||||
|
||||
Select the _Inbox_ for the account you wish to filter and select the toolbar menu item at _Tools > Message_Filters_.
|
||||
Select the *Inbox* for the account you wish to filter and select the toolbar menu item at *Tools > Message_Filters*.
|
||||
|
||||
![Message_Filters menu location][5]
|
||||
![][5]
|
||||
|
||||
The _Message Filters_ menu appears and is set to your pre-selected account as indicated at the top in the selection menu labelled _Filters for:_.
|
||||
The *Message Filters* menu appears and is set to your pre-selected account as indicated at the top in the selection menu labelled *Filters for:*.
|
||||
|
||||
![Message Filters menu][6]
|
||||
![][6]
|
||||
|
||||
Previously created filters, if any, are listed beneath the account name in the “_Filter Name”_ column. To the right of this list are controls that let you modify the filters selected. These controls are activated when you select a filter. More on this later.
|
||||
Previously created filters, if any, are listed beneath the account name in the “*Filter Name”*column. To the right of this list are controls that let you modify the filters selected. These controls are activated when you select a filter. More on this later.
|
||||
|
||||
Start creating your filter as follows:
|
||||
|
||||
1. Verify the correct account has been pre-selected. It may be changed if necessary.
|
||||
2. Select _New…_ from the menu list at the right.
|
||||
1. Verify the correct account has been pre-selected. It may be changed if necessary.
|
||||
2. Select New… from the menu list at the right.
|
||||
|
||||
|
||||
|
||||
When you select _New_ you will see the _Filter Rules_ menu where you define your filter. Note that when using _New…_ you have the option to copy an existing filter to use as a template or to simply duplicate the settings.
|
||||
When you select *New* you will see the *Filter Rules*menu where you define your filter. Note that when using *New…* you have the option to copy an existing filter to use as a template or to simply duplicate the settings.
|
||||
|
||||
Filter rules are made up of three things, the “property” to be tested, the “test”, and the “value” to be tested against. Once the condition is met, the “action” is performed.
|
||||
|
||||
![Message Filters menu][7]
|
||||
![][7]
|
||||
|
||||
Complete this filter as follows:
|
||||
|
||||
1. Enter an appropriate name in the textbox labelled _Filter name:_
|
||||
2. Select the property _From_ in the left drop down menu, if not set.
|
||||
3. Leave the test set to _contains_.
|
||||
4. Enter the value, in this case the email address of the sender.
|
||||
1. Enter an appropriate name in the textbox labelled Filter name:
|
||||
2. Select the property From in the left drop down menu, if not set.
|
||||
3. Leave the test set to contains.
|
||||
4. Enter the value, in this case the email address of the sender.
|
||||
|
||||
Under the *Perform these actions:* section at the bottom, create an action rule to move the message and choose the destination.
|
||||
|
||||
1. Select Move Messages to from the left end of the action line.
|
||||
2. Select Choose Folder… and select Local Folders > New York Times.
|
||||
3. Select OK.
|
||||
|
||||
Under the _Perform these actions:_ section at the bottom, create an action rule to move the message and choose the destination.
|
||||
By default the **Apply filter when:** is set to *Manually Run* and *Getting New Mail:*. This means that when new mail appears in the Inbox for this account the filter will be applied and you may run it manually at any time, if necessary. There are other options available but they are too numerous to be discussed in this introduction. They are, however, for the most part self explanatory.
|
||||
|
||||
1. Select _Move Messages to_ from the left end of the action line.
|
||||
2. Select _Choose Folder…_ and select _Local Folders > New York Times_.
|
||||
3. Select _OK_.
|
||||
If more than one rule or action is to be created during the same session, the “+” to the right of each entry provides that option. Additional property, test, and value entries can be added. If more than one rule is created, make certain that the appropriate option for *Match all of the following* and *Match any of the following* is selected. In this example the choice does not matter since we are only setting one filter.
|
||||
|
||||
After selecting *OK,*the *Message Filters* menu is displayed again showing your newly created filter. Note that the menu items on the right side of the menu are now active for *Edit…* and *Delete.*
|
||||
|
||||
![][8]
|
||||
|
||||
By default the **Apply filter when:** is set to _Manually Run_ and _Getting New Mail:_. This means that when new mail appears in the Inbox for this account the filter will be applied and you may run it manually at any time, if necessary. There are other options available but they are too numerous to be discussed in this introduction. They are, however, for the most part self explanatory.
|
||||
Also notice the message *“Enabled filters are run automatically in the order shown below”*. If there are multiple filters the order is changed by selecting the one to be moved and using the *Move to Top, Move Up, Move Down,*or*Move to Bottom* buttons. The order can change the destination of your messages so consider the tests used in each filter carefully when deciding the order.
|
||||
|
||||
If more than one rule or action is to be created during the same session, the “+” to the right of each entry provides that option. Additional property, test, and value entries can be added. If more than one rule is created, make certain that the appropriate option for _Match all of the following_ and _Match any of the following_ is selected. In this example the choice does not matter since we are only setting one filter.
|
||||
|
||||
After selecting _OK,_ the _Message Filters_ menu is displayed again showing your newly created filter. Note that the menu items on the right side of the menu are now active for _Edit…_ and _Delete._
|
||||
|
||||
![First filter in the Message Filters menu][8]
|
||||
|
||||
Also notice the message _“Enabled filters are run automatically in the order shown below”_. If there are multiple filters the order is changed by selecting the one to be moved and using the _Move to Top, Move Up, Move Down,_ or _Move to Bottom_ buttons. The order can change the destination of your messages so consider the tests used in each filter carefully when deciding the order.
|
||||
|
||||
Since you have just created this filter you may wish to use the _Run Now_ button to run your newly created filter on the Inbox shown to the left of the button.
|
||||
Since you have just created this filter you may wish to use the *Run Now* button to run your newly created filter on the Inbox shown to the left of the button.
|
||||
|
||||
### Filter creation based on a message
|
||||
|
||||
An alternative creation technique is to select a message from the message pane and use the _Create Filter From Message…_ option from the menu bar.
|
||||
An alternative creation technique is to select a message from the message pane and use the *Create Filter From Message…* option from the menu bar.
|
||||
|
||||
In this example the filter will use two rules to select the messages: the email address and a text string in the Subject line of the email. Start as follows:
|
||||
In this example the filter will use two rules to select the messages: the email address and a text string in the Subject line of the email. Start as follows:
|
||||
|
||||
1. Select a message in the message page.
|
||||
2. Select the filter options on the toolbar at _Message > Create Filter From Message…_.
|
||||
1. Select a message in the message page.
|
||||
2. Select the filter options on the toolbar at Message > Create Filter From Message….
|
||||
|
||||
![][9]
|
||||
|
||||
|
||||
![Create new filters from Messages][9]
|
||||
|
||||
The pre-selected message, highlighted in grey in the message pane above, determines the account used and _Create Filter From Message…_ takes you directly to the _Filter Rules_ menu.
|
||||
The pre-selected message, highlighted in grey in the message pane above, determines the account used and *Create Filter From Message…* takes you directly to the *Filter Rules* menu.
|
||||
|
||||
![][10]
|
||||
|
||||
The property (_From_), test (_is_), and value (email) are pre-set for you as shown in the image above. Complete this filter as follows:
|
||||
The property (*From*), test (*is*), and value (email) are pre-set for you as shown in the image above. Complete this filter as follows:
|
||||
|
||||
1. Enter an appropriate name in the textbox labelled _Filter name:_. _COVID_ is the name in this case.
|
||||
2. Check that the property is _From_.
|
||||
3. Verify the test is set to _is_.
|
||||
4. Confirm that the value for the email address is from the correct sender.
|
||||
5. Select the “+” to the right of the _From_ rule to create a new filter rule.
|
||||
6. In the new rule, change the default property entry _From_ to _Subject_ using the pulldown menu.
|
||||
7. Set the test to _contains_.
|
||||
8. Enter the value text to be matched in the Email “Subject” line. In this case _COVID_.
|
||||
1. Enter an appropriate name in the textbox labelled Filter name:. COVID is the name in this case.
|
||||
2. Check that the property is From.
|
||||
3. Verify the test is set to is.
|
||||
4. Confirm that the value for the email address is from the correct sender.
|
||||
5. Select the “+” to the right of the From rule to create a new filter rule.
|
||||
6. In the new rule, change the default property entry From to Subject using the pulldown menu.
|
||||
7. Set the test to contains.
|
||||
8. Enter the value text to be matched in the Email “Subject” line. In this case COVID.
|
||||
|
||||
Since we left the *Match all of the following* item checked, each message will be from the address chosen AND will have the text *COVID* in the email subject line.
|
||||
|
||||
Now use the action rule to choose the destination for the messages under the *Perform these actions:* section at the bottom:
|
||||
|
||||
Since we left the _Match all of the following_ item checked, each message will be from the address chosen AND will have the text _COVID_ in the email subject line.
|
||||
1. Select Move Messages to from the left menu.
|
||||
2. Select Choose Folder… and select Local Folders > COVID in Scotland. (This destination was created before this example was started. There was no magic here.)
|
||||
3. Select OK.
|
||||
|
||||
Now use the action rule to choose the destination for the messages under the _Perform these actions:_ section at the bottom:
|
||||
|
||||
1. Select _Move Messages to_ from the left menu.
|
||||
2. Select _Choose Folder…_ and select _Local Folders > COVID in Scotland_. (This destination was created before this example was started. There was no magic here.)
|
||||
3. Select _OK_.
|
||||
|
||||
|
||||
|
||||
_OK_ will cause the _Message Filters_ menu to appear, again, verifying that the new filter has been created.
|
||||
*OK* will cause the *Message Filters* menu to appear, again, verifying that the new filter has been created.
|
||||
|
||||
### The Message Filters menu
|
||||
|
||||
All the message filters you create will appear in the _Message Filters_ menu. Recall that the _Message Filters_ is available in the menu bar at _Tools > Message Filters_.
|
||||
All the message filters you create will appear in the *Message Filters* menu. Recall that the *Message Filters* is available in the menu bar at *Tools > Message Filters*.
|
||||
|
||||
Once you have created filters there are several options to manage them. To change a filter, select the filter in question and click on the _Edit_ button. This will take you back to the _Filter Rules_ menu for that filter. As mentioned earlier, you can change the order in which the rules are apply here using the _Move_ buttons. Disable a filter by clicking on the check mark in the _Enabled_ column.
|
||||
Once you have created filters there are several options to manage them. To change a filter, select the filter in question and click on the *Edit* button. This will take you back to the *Filter Rules* menu for that filter. As mentioned earlier, you can change the order in which the rules are apply here using the *Move* buttons. Disable a filter by clicking on the check mark in the *Enabled* column.
|
||||
|
||||
![][11]
|
||||
|
||||
The _Run Now_ button will execute the selected filter immediately. You may also run your filter from the menu bar using _Tools > Run Filters on Folder_ or _Tools > Run Filters on Message_.
|
||||
The *Run Now* button will execute the selected filter immediately. You may also run your filter from the menu bar using *Tools > Run Filters on Folder* or *Tools > Run Filters on Message*.
|
||||
|
||||
### Next step
|
||||
|
||||
This article hasn’t covered every feature available for message filtering but hopefully it provides enough information for you to get started. Places for further investigation are the “property”, “test”, and “actions” in the _Filter menu_ as well as the settings there for when your filter is to be run, _Archiving, After Sending,_ and _Periodically_.
|
||||
This article hasn’t covered every feature available for message filtering but hopefully it provides enough information for you to get started. Places for further investigation are the “property”, “test”, and “actions” in the *Filter menu* as well as the settings there for when your filter is to be run, *Archiving, After Sending,* and *Periodically*.
|
||||
|
||||
### References
|
||||
|
||||
Mozilla: [Organize][12] [Your Messages][12] [by Using Filters][12]
|
||||
Mozilla: [Organize][12][Your Messages][13][by Using Filters][14]
|
||||
|
||||
MozillaZine: [Message][13] [Filters][13]
|
||||
MozillaZine: [Message][15][Filters][16]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://fedoramagazine.org/introduction-to-thunderbird-mail-filters/
|
||||
|
||||
作者:[Richard England][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://fedoramagazine.org/author/rlengland/
|
||||
[b]: https://github.com/lujun9972
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://fedoramagazine.org/wp-content/uploads/2021/01/Tbird_mail_filters-1-816x345.jpg
|
||||
[2]: https://fedoramagazine.org/wp-content/uploads/2021/01/Image_001-1024x613.png
|
||||
[3]: https://fedoramagazine.org/wp-content/uploads/2021/01/Image_New_Folder.png
|
||||
@ -171,4 +158,7 @@ via: https://fedoramagazine.org/introduction-to-thunderbird-mail-filters/
|
||||
[10]: https://fedoramagazine.org/wp-content/uploads/2021/01/Filter_rules_2-1.png
|
||||
[11]: https://fedoramagazine.org/wp-content/uploads/2021/01/Message_Filters_2nd_entry.png
|
||||
[12]: https://support.mozilla.org/en-US/kb/organize-your-messages-using-filters
|
||||
[13]: http://kb.mozillazine.org/Filters_%28Thunderbird%29
|
||||
[13]: https://support.mozilla.org/en-US/kb/organize-your-messages-using-filters
|
||||
[14]: https://support.mozilla.org/en-US/kb/organize-your-messages-using-filters
|
||||
[15]: http://kb.mozillazine.org/Filters_%28Thunderbird%29
|
||||
[16]: http://kb.mozillazine.org/Filters_%28Thunderbird%29
|
||||
|
@ -1,46 +1,43 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Interview with Shuah Khan, Kernel Maintainer & Linux Fellow)
|
||||
[#]: via: (https://www.linux.com/news/interview-with-shuah-khan-kernel-maintainer-linux-fellow/)
|
||||
[#]: author: (The Linux Foundation https://www.linuxfoundation.org/en/blog/interview-with-shuah-khan-kernel-maintainer-linux-fellow/)
|
||||
[#]: subject: "Interview with Shuah Khan, Kernel Maintainer & Linux Fellow"
|
||||
[#]: via: "https://www.linux.com/news/interview-with-shuah-khan-kernel-maintainer-linux-fellow/"
|
||||
[#]: author: "The Linux Foundation https://www.linuxfoundation.org/en/blog/interview-with-shuah-khan-kernel-maintainer-linux-fellow/"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
Interview with Shuah Khan, Kernel Maintainer & Linux Fellow
|
||||
======
|
||||
|
||||
![][1]
|
||||
|
||||
_Jason Perlow, Director of Project Insights and Editorial Content at the Linux Foundation, had an opportunity to speak with Shuah Khan about her experiences as a woman in the technology industry. She discusses how mentorship can improve the overall diversity and makeup of open source projects, why software maintainers are important for the health of open source projects such as the Linux kernel, and how language inclusivity and codes of conduct can improve relationships and communication between software maintainers and individual contributors._
|
||||
Jason Perlow, Director of Project Insights and Editorial Content at the Linux Foundation, had an opportunity to speak with Shuah Khan about her experiences as a woman in the technology industry. She discusses how mentorship can improve the overall diversity and makeup of open source projects, why software maintainers are important for the health of open source projects such as the Linux kernel, and how language inclusivity and codes of conduct can improve relationships and communication between software maintainers and individual contributors.
|
||||
|
||||
**JP:** So, Shuah, I know you wear many different hats at the Linux Foundation. What do you call yourself around here these days?
|
||||
|
||||
**SK:** <laughs> Well, I primarily call myself a Kernel Maintainer & Linux Fellow. In addition to that, I focus on two areas that are important to the continued health and sustainability of the open source projects in the Linux ecosystem. The first one is bringing more women into the Kernel community, and additionally, I am leading the mentorship program efforts overall at the Linux Foundation. And in that role, in addition to the Linux Kernel Mentorship, we are looking at how the Linux Foundation mentorship program is working overall, how it is scaling. I make sure the [LFX Mentorship][2] platform scales and serves diverse mentees and mentors’ needs in this role.
|
||||
**SK:** <laughs> Well, I primarily call myself a Kernel Maintainer & Linux Fellow. In addition to that, I focus on two areas that are important to the continued health and sustainability of the open source projects in the Linux ecosystem. The first one is bringing more women into the Kernel community, and additionally, I am leading the mentorship program efforts overall at the Linux Foundation. And in that role, in addition to the Linux Kernel Mentorship, we are looking at how the Linux Foundation mentorship program is working overall, how it is scaling. I make sure the [LFX Mentorship][1] platform scales and serves diverse mentees and mentors’ needs in this role.
|
||||
|
||||
The LF mentorships program includes several projects in the Linux kernel, LFN, HyperLedger, Open MainFrame, OpenHPC, and other technologies. [The Linux Foundation’s Mentorship Programs][3] are designed to help developers with the necessary skills–many of whom are first-time open source contributors–experiment, learn, and contribute effectively to open source communities.
|
||||
The LF mentorships program includes several projects in the Linux kernel, LFN, HyperLedger, Open MainFrame, OpenHPC, and other technologies. [The Linux Foundation’s Mentorship Programs][2] are designed to help developers with the necessary skills–many of whom are first-time open source contributors–experiment, learn, and contribute effectively to open source communities.
|
||||
|
||||
The mentorship program has been successful in its mission to train new developers and make these talented pools of prospective employees trained by experts to employers. Several graduated mentees have found jobs. New developers have improved the quality and security of various open source projects, including the Linux kernel. Several Linux kernel bugs were fixed, a new subsystem mentor was added, and a new driver maintainer is now part of the Linux kernel community. My sincere thanks to all our mentors for volunteering to share their expertise.
|
||||
|
||||
**JP:** How long have you been working on the Kernel?
|
||||
|
||||
**SK:** Since 2010, or 2011, I got involved in the [Android Mainlining project][4]. My [first patch removed the Android pmem driver][5].
|
||||
**SK:** Since 2010, or 2011, I got involved in the [Android Mainlining project][3]. My [first patch removed the Android pmem driver][4].
|
||||
|
||||
**JP:** Wow! Is there any particular subsystem that you specialize in?
|
||||
|
||||
**SK:** I am a self described generalist. I maintain the [kernel self-test][6] subsystem, the [USB over IP driver][7], [usbip tool][8], and the [cpupower][9] tool. I contributed to the media subsystem working on [Media Controller Device Allocator API][10] to resolve shared device resource management problems across device drivers from different subsystems.
|
||||
**SK:** I am a self described generalist. I maintain the [kernel self-test][5] subsystem, the [USB over IP driver][6], [usbip tool][7], and the [cpupower][8] tool. I contributed to the media subsystem working on [Media Controller Device Allocator API][9] to resolve shared device resource management problems across device drivers from different subsystems.
|
||||
|
||||
**JP:** Hey, I’ve [actually used the USB over IP driver][11] when I worked at Microsoft on Azure. And also, when I’ve used AWS and Google Compute.
|
||||
**JP:** Hey, I’ve [actually used the USB over IP driver][10] when I worked at Microsoft on Azure. And also, when I’ve used AWS and Google Compute.
|
||||
|
||||
**SK:** It’s a small niche driver used in cloud computing. Docker and other containers use that driver heavily. That’s how they provide remote access to USB devices on the server to export devices to be imported by other systems for use.
|
||||
|
||||
**JP:** I initially used it for IoT kinds of stuff in the embedded systems space. Were you the original lead developer on it, or was it one of those things you fell into because nobody else was maintaining it?
|
||||
|
||||
**SK:** Well, twofold. I was looking at USB over IP because I like that technology. it just so happened the driver was brought from the staging tree into the Mainline kernel, I volunteered at the time to maintain it. Over the last few years, we discovered some security issues with it, because it handles a lot of userspace data, so I had a lot of fun fixing all of those. <laugh>.
|
||||
**SK:** Well, twofold. I was looking at USB over IP because I like that technology. it just so happened the driver was brought from the staging tree into the Mainline kernel, I volunteered at the time to maintain it. Over the last few years, we discovered some security issues with it, because it handles a lot of userspace data, so I had a lot of fun fixing all of those. <laugh>.
|
||||
|
||||
**JP:** What drew you into the Linux operating system, and what drew you into the kernel development community in the first place?
|
||||
|
||||
**SK:** Well, I have been doing kernel development for a very long time. I worked on the [LynxOS RTOS][12], a while back, and then HP/UX, when I was working at HP, after which I transitioned into doing open source development — the [OpenHPI][13] project, to support HP’s rack server hardware, and that allowed me to work much more closely with Linux on the back end. And at some point, I decided I wanted to work with the kernel and become part of the Linux kernel community. I started as an independent contributor.
|
||||
**SK:** Well, I have been doing kernel development for a very long time. I worked on the [LynxOS RTOS][11], a while back, and then HP/UX, when I was working at HP, after which I transitioned into doing open source development — the [OpenHPI][12] project, to support HP’s rack server hardware, and that allowed me to work much more closely with Linux on the back end. And at some point, I decided I wanted to work with the kernel and become part of the Linux kernel community. I started as an independent contributor.
|
||||
|
||||
**JP:** Maybe it just displays my own ignorance, but you are the first female, hardcore Linux kernel developer I have ever met. I mean, I had met female core OS developers before — such as when I was at Microsoft and IBM — but not for Linux. Why do you suppose we lack women and diversity in general when participating in open source and the technology industry overall?
|
||||
|
||||
@ -52,9 +49,9 @@ There’s a natural resistance to choosing certain professions that you have to
|
||||
|
||||
**SK:** Yes.
|
||||
|
||||
**JP:** It’s funny; my wife really likes this [Netflix show about matchmaking in India][14]. Are you familiar with it?
|
||||
**JP:** It’s funny; my wife really likes this [Netflix show about matchmaking in India][13]. Are you familiar with it?
|
||||
|
||||
**SK:** <laughs> Yes I enjoyed the series, and [A Suitable Girl][15] documentary film that follows three women as they navigate making decisions about their careers and family obligations.
|
||||
**SK:** <laughs> Yes I enjoyed the series, and [A Suitable Girl][14] documentary film that follows three women as they navigate making decisions about their careers and family obligations.
|
||||
|
||||
**JP:** For many Americans, this is our first introduction to what home life is like for Indian people. But many of the women featured on this show are professionals, such as doctors, lawyers, and engineers. And they are very ambitious, but of course, the family tries to set them up in a marriage to find a husband for them that is compatible. As a result, you get to learn about the traditional values and roles they still want women to play there — while at the same time, many women are coming out of higher learning institutions in that country that are seeking technical careers.
|
||||
|
||||
@ -62,11 +59,11 @@ There’s a natural resistance to choosing certain professions that you have to
|
||||
|
||||
**JP:** Women in technical and STEM professions are becoming much more prominent in other countries, such as China, Japan, and Korea. For some reason, in the US, I tend to see more women enter the medical profession than hard technology — and it might be a level of effort and perceived reward thing. You can spend eight years becoming a medical doctor or eight years becoming a scientist or an engineer, and it can be equally difficult, but the compensation at the end may not be the same. It’s expensive to get an education, and it takes a long time and hard work, regardless of the professional discipline.
|
||||
|
||||
**SK:** I have also heard that women also like to enter professions where they can make a difference in the world — a human touch, if you will. So that may translate to them choosing careers where they can make a larger impact on people — and they may view careers in technology as not having those same attributes. Maybe when we think about attracting women to technology fields, we might have to promote technology aspects that make a difference. That may be changing now, such as the [LF Public Health][16] (LFPH) project we kicked off last year. And with [LF AI & Data Foundation][17], we are also making a difference in people’s lives, such as [detecting earthquakes][18] or [analyzing climate change][19]. If we were to promote projects such as these, we might draw more women in.
|
||||
**SK:** I have also heard that women also like to enter professions where they can make a difference in the world — a human touch, if you will. So that may translate to them choosing careers where they can make a larger impact on people — and they may view careers in technology as not having those same attributes. Maybe when we think about attracting women to technology fields, we might have to promote technology aspects that make a difference. That may be changing now, such as the [LF Public Health][15] (LFPH) project we kicked off last year. And with [LF AI & Data Foundation][16], we are also making a difference in people’s lives, such as [detecting earthquakes][17] or [analyzing climate change][18]. If we were to promote projects such as these, we might draw more women in.
|
||||
|
||||
**JP:** So clearly, one of the areas of technology where you can make a difference is in open source, as the LF is hosting some very high-concept and existential types of projects such as [LF Energy][20], for example — I had no idea what was involved in it and what its goals were until I spoke to [Shuli Goodman][21] in-depth about it. With the mentorship program, I assume we need this to attract fresh talent — because as folks like us get older and retire, and they exit the field, we need new people to replace them. So I assume mentorship, for the Linux Foundation, is an investment in our own technologies, correct?
|
||||
**JP:** So clearly, one of the areas of technology where you can make a difference is in open source, as the LF is hosting some very high-concept and existential types of projects such as [LF Energy][19], for example — I had no idea what was involved in it and what its goals were until I spoke to [Shuli Goodman][20] in-depth about it. With the mentorship program, I assume we need this to attract fresh talent — because as folks like us get older and retire, and they exit the field, we need new people to replace them. So I assume mentorship, for the Linux Foundation, is an investment in our own technologies, correct?
|
||||
|
||||
**SK:** Correct. Bringing in new developers into the fold is the primary purpose, of course — and at the same time, I view the LF as taking on mentorship provides that neutral, level playing field across the industry for all open source projects. Secondly, we offer a self-service platform, [LFX Mentorship][22], where anyone can come in and start their project. So when the COVID-19 pandemic began, we [expanded this program to help displaced people][3] — students, et cetera, and less visible projects. Not all projects typically get as much funding or attention as others do — such as a Kubernetes or Linux kernel — among the COVID mentorship program projects we are funding. I am particularly proud of supporting a climate change-related project, [Using Machine Learning to Predict Deforestation][23].
|
||||
**SK:** Correct. Bringing in new developers into the fold is the primary purpose, of course — and at the same time, I view the LF as taking on mentorship provides that neutral, level playing field across the industry for all open source projects. Secondly, we offer a self-service platform, [LFX Mentorship][21], where anyone can come in and start their project. So when the COVID-19 pandemic began, we [expanded this program to help displaced people][22] — students, et cetera, and less visible projects. Not all projects typically get as much funding or attention as others do — such as a Kubernetes or Linux kernel — among the COVID mentorship program projects we are funding. I am particularly proud of supporting a climate change-related project, [Using Machine Learning to Predict Deforestation][23].
|
||||
|
||||
The self-service approach allows us to fund and add new developers to projects where they are needed. The LF mentorships are remote work opportunities that are accessible to developers around the globe. We see people sign up for mentorship projects from places we haven’t seen before, such as Africa, and so on, thus creating a level playing field.
|
||||
|
||||
@ -122,43 +119,43 @@ Talking about backpacking reminded me of the two-day, 22-mile backpacking trip d
|
||||
|
||||
**JP:** Awesome. I enjoyed talking to you today. So happy I finally got to meet you virtually.
|
||||
|
||||
The post [Interview with Shuah Khan, Kernel Maintainer & Linux Fellow][33] appeared first on [Linux Foundation][34].
|
||||
The post [Interview with Shuah Khan, Kernel Maintainer & Linux Fellow][33] appeared first on [Linux Foundation][34].
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://www.linux.com/news/interview-with-shuah-khan-kernel-maintainer-linux-fellow/
|
||||
|
||||
作者:[The Linux Foundation][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://www.linuxfoundation.org/en/blog/interview-with-shuah-khan-kernel-maintainer-linux-fellow/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://www.linux.com/wp-content/uploads/2021/01/3E9C3E02-5F59-4A99-AD4A-814C7B8737A9_1_105_c.jpeg
|
||||
[2]: https://lfx.linuxfoundation.org/tools/mentorship/
|
||||
[3]: https://linuxfoundation.org/about/diversity-inclusivity/mentorship/
|
||||
[4]: https://elinux.org/Android_Mainlining_Project
|
||||
[5]: https://lkml.org/lkml/2012/1/26/368
|
||||
[6]: https://www.kernel.org/doc/html/v4.15/dev-tools/kselftest.html
|
||||
[7]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/usb/usbip
|
||||
[8]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/usb/usbip
|
||||
[9]: https://www.systutorials.com/docs/linux/man/1-cpupower/
|
||||
[10]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/media/mc/mc-dev-allocator.c
|
||||
[11]: https://www.linux-magazine.com/Issues/2018/208/Tutorial-USB-IP
|
||||
[12]: https://en.wikipedia.org/wiki/LynxOS
|
||||
[13]: http://www.openhpi.org/Developers
|
||||
[14]: https://www.netflix.com/title/80244565
|
||||
[15]: https://en.wikipedia.org/wiki/A_Suitable_Girl_(film)
|
||||
[16]: https://www.lfph.io/
|
||||
[17]: https://lfaidata.foundation/
|
||||
[18]: https://openeew.com/
|
||||
[19]: https://www.os-climate.org/
|
||||
[20]: https://www.lfenergy.org/
|
||||
[21]: mailto:sgoodman@contractor.linuxfoundation.org
|
||||
[22]: https://mentorship.lfx.linuxfoundation.org/
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://lfx.linuxfoundation.org/tools/mentorship/
|
||||
[2]: https://linuxfoundation.org/about/diversity-inclusivity/mentorship/
|
||||
[3]: https://elinux.org/Android_Mainlining_Project
|
||||
[4]: https://lkml.org/lkml/2012/1/26/368
|
||||
[5]: https://www.kernel.org/doc/html/v4.15/dev-tools/kselftest.html
|
||||
[6]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/usb/usbip
|
||||
[7]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/usb/usbip
|
||||
[8]: https://www.systutorials.com/docs/linux/man/1-cpupower/
|
||||
[9]: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/media/mc/mc-dev-allocator.c
|
||||
[10]: https://www.linux-magazine.com/Issues/2018/208/Tutorial-USB-IP
|
||||
[11]: https://en.wikipedia.org/wiki/LynxOS
|
||||
[12]: http://www.openhpi.org/Developers
|
||||
[13]: https://www.netflix.com/title/80244565
|
||||
[14]: https://en.wikipedia.org/wiki/A_Suitable_Girl_(film)
|
||||
[15]: https://www.lfph.io/
|
||||
[16]: https://lfaidata.foundation/
|
||||
[17]: https://openeew.com/
|
||||
[18]: https://www.os-climate.org/
|
||||
[19]: https://www.lfenergy.org/
|
||||
[20]: https://www.linux.com/mailto:sgoodman@contractor.linuxfoundation.org
|
||||
[21]: https://mentorship.lfx.linuxfoundation.org/
|
||||
[22]: https://linuxfoundation.org/about/diversity-inclusivity/mentorship/
|
||||
[23]: https://mentorship.lfx.linuxfoundation.org/project/926665ac-9b96-45aa-bb11-5d99096be870
|
||||
[24]: https://www.linuxfoundation.org/en/blog/preventing-supply-chain-attacks-like-solarwinds/
|
||||
[25]: https://www.linuxfoundation.org/en/press-release/new-open-source-contributor-report-from-linux-foundation-and-harvard-identifies-motivations-and-opportunities-for-improving-software-security/
|
@ -92,7 +92,7 @@ The system is trying to remove an item that does not exist in the basket, and it
|
||||
|
||||
```
|
||||
public int RemoveItem(Hashtable item) {
|
||||
if(basket.IndexOf(item) >= 0) {
|
||||
if(basket.IndexOf(item) >= 0) {
|
||||
basket.RemoveAt(basket.IndexOf(item));
|
||||
}
|
||||
return basket.Count;
|
||||
|
@ -86,7 +86,7 @@ Implement this processing logic in the `ShippingAPI` class:
|
||||
```
|
||||
private double Calculate10PercentDiscount(double total) {
|
||||
double discount = 0.00;
|
||||
if(total > 500.00) {
|
||||
if(total > 500.00) {
|
||||
discount = (total/100) * 10;
|
||||
}
|
||||
return discount;
|
||||
|
@ -42,7 +42,7 @@ nvme_core.default_ps_max_latency_us=0
|
||||
|
||||
In the end I upgraded my main workstation so I could repurpose its existing Samsung EVO 960 for the HoneyComb which worked much better.
|
||||
|
||||
After some fidgeting I was able to install Fedora but it became apparent that the integrated network ports still don’t work with the mainline kernel. The NXP tech is great but requires a custom kernel build and tooling. Some earlier blogs got around this with a USB->RJ45 Ethernet adapter which works fine. Hopefully network support will be mainlined soon, but for now I snagged a kernel SRPM from the helpful engineers on Discord. With the custom kernel the 1Gbe NIC worked fine, but it turns out the SFP+ ports need more configuration. They won’t be recognized as interfaces until you use NXP’s _restool_ utility to map ports to their usage. In this case just a runtime mapping of _dmap -> dni_ was required. This is NXP’s way of mapping a MAC to a network interface via IOCTL commands. The restool binary isn’t provided either and must be built from source. It then layers on management scripts which use cheeky $arg0 references for redirection to call the restool binary with complex arguments.
|
||||
After some fidgeting I was able to install Fedora but it became apparent that the integrated network ports still don’t work with the mainline kernel. The NXP tech is great but requires a custom kernel build and tooling. Some earlier blogs got around this with a USB->RJ45 Ethernet adapter which works fine. Hopefully network support will be mainlined soon, but for now I snagged a kernel SRPM from the helpful engineers on Discord. With the custom kernel the 1Gbe NIC worked fine, but it turns out the SFP+ ports need more configuration. They won’t be recognized as interfaces until you use NXP’s _restool_ utility to map ports to their usage. In this case just a runtime mapping of _dmap -> dni_ was required. This is NXP’s way of mapping a MAC to a network interface via IOCTL commands. The restool binary isn’t provided either and must be built from source. It then layers on management scripts which use cheeky $arg0 references for redirection to call the restool binary with complex arguments.
|
||||
|
||||
Since I was starting to accumulate quite a few custom packages it was apparent that a COPR repo was needed to simplify this for Fedora. If you’re not familiar with COPR I think it’s one of Fedora’s finest resources. This repo contains the uefi build (currently failing build), 5.10.5 kernel built with network support, and the restool binary with supporting scripts. I also added a oneshot systemd unit to enable the SFP+ ports on boot:
|
||||
```
|
||||
|
@ -85,8 +85,8 @@ Describe the newly created namespace:
|
||||
```
|
||||
[root@master ~]# kubectl describe namespace test
|
||||
Name: test
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
Status: Active
|
||||
No resource quota.
|
||||
No LimitRange resource.
|
||||
@ -233,8 +233,8 @@ Verify the Roles:
|
||||
```
|
||||
$ kubectl describe roles -n test
|
||||
Name: list-deployments
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
PolicyRule:
|
||||
Resources Non-Resource URLs Resource Names Verbs
|
||||
--------- ----------------- -------------- -----
|
||||
|
@ -1,57 +1,51 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Draw Mandelbrot fractals with GIMP scripting)
|
||||
[#]: via: (https://opensource.com/article/21/2/gimp-mandelbrot)
|
||||
[#]: author: (Cristiano L. Fontana https://opensource.com/users/cristianofontana)
|
||||
[#]: subject: "Draw Mandelbrot fractals with GIMP scripting"
|
||||
[#]: via: "https://opensource.com/article/21/2/gimp-mandelbrot"
|
||||
[#]: author: "Cristiano L. Fontana https://opensource.com/users/cristianofontana"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
Draw Mandelbrot fractals with GIMP scripting
|
||||
======
|
||||
Create complex mathematical images with GIMP's Script-Fu language.
|
||||
|
||||
![Painting art on a computer screen][1]
|
||||
|
||||
Image by: Opensource.com
|
||||
|
||||
The GNU Image Manipulation Program ([GIMP][2]) is my go-to solution for image editing. Its toolset is very powerful and convenient, except for doing [fractals][3], which is one thing you cannot draw by hand easily. These are fascinating mathematical constructs that have the characteristic of being [self-similar][4]. In other words, if they are magnified in some areas, they will look remarkably similar to the unmagnified picture. Besides being interesting, they also make very pretty pictures!
|
||||
|
||||
![Portion of a Mandelbrot fractal using GIMPs Coldfire palette][5]
|
||||
![Rotated and magnified portion of the Mandelbrot set using Firecode][5]
|
||||
|
||||
Portion of a Mandelbrot fractal using GIMP's Coldfire palette (Cristiano Fontana, [CC BY-SA 4.0][6])
|
||||
GIMP can be automated with [Script-Fu][6] to do [batch processing of images][7] or create complicated procedures that are not practical to do by hand; drawing fractals falls in the latter category. This tutorial will show how to draw a representation of the [Mandelbrot fractal][8] using GIMP and Script-Fu.
|
||||
|
||||
GIMP can be automated with [Script-Fu][7] to do [batch processing of images][8] or create complicated procedures that are not practical to do by hand; drawing fractals falls in the latter category. This tutorial will show how to draw a representation of the [Mandelbrot fractal][9] using GIMP and Script-Fu.
|
||||
![Mandelbrot set drawn using GIMP's Firecode palette][9]
|
||||
|
||||
![Mandelbrot set drawn using GIMP's Firecode palette][10]
|
||||
|
||||
Portion of a Mandelbrot fractal using GIMP's Firecode palette. (Cristiano Fontana, [CC BY-SA 4.0][6])
|
||||
|
||||
![Rotated and magnified portion of the Mandelbrot set using Firecode.][11]
|
||||
|
||||
Rotated and magnified portion of the Mandelbrot set using the Firecode palette. (Cristiano Fontana, [CC BY-SA 4.0][6])
|
||||
![Rotated and magnified portion of the Mandelbrot set using Firecode.][10]
|
||||
|
||||
In this tutorial, you will write a script that creates a layer in an image and draws a representation of the Mandelbrot set with a colored environment around it.
|
||||
|
||||
### What is the Mandelbrot set?
|
||||
|
||||
Do not panic! I will not go into too much detail here. For the more math-savvy, the Mandelbrot set is defined as the set of [complex numbers][12] _a_ for which the succession
|
||||
Do not panic! I will not go into too much detail here. For the more math-savvy, the Mandelbrot set is defined as the set of [complex numbers][11] *a* for which the succession
|
||||
|
||||
_zn+1 = zn2 + a_
|
||||
zn+1 = zn2 + a
|
||||
|
||||
does not diverge when starting from _z₀ = 0_.
|
||||
does not diverge when starting from *z₀ = 0*.
|
||||
|
||||
In reality, the Mandelbrot set is the fancy-looking black blob in the pictures; the nice-looking colors are outside the set. They represent how many iterations are required for the magnitude of the succession of numbers to pass a threshold value. In other words, the color scale shows how many steps are required for the succession to pass an upper-limit value.
|
||||
|
||||
### GIMP's Script-Fu
|
||||
|
||||
[Script-Fu][7] is the scripting language built into GIMP. It is an implementation of the [Scheme programming language][13].
|
||||
[Script-Fu][12] is the scripting language built into GIMP. It is an implementation of the [Scheme programming language][13].
|
||||
|
||||
If you want to get more acquainted with Scheme, GIMP's documentation offers an [in-depth tutorial][14]. I also wrote an article about [batch processing images][8] using Script-Fu. Finally, the Help menu offers a Procedure Browser with very extensive documentation with all of Script-Fu's functions described in detail.
|
||||
If you want to get more acquainted with Scheme, GIMP's documentation offers an [in-depth tutorial][14]. I also wrote an article about [batch processing images][15] using Script-Fu. Finally, the Help menu offers a Procedure Browser with very extensive documentation with all of Script-Fu's functions described in detail.
|
||||
|
||||
![GIMP Procedure Browser][15]
|
||||
|
||||
(Cristiano Fontana, [CC BY-SA 4.0][6])
|
||||
|
||||
Scheme is a Lisp-like language, so a major characteristic is that it uses a [prefix notation][16] and a [lot of parentheses][17]. Functions and operators are applied to a list of operands by prefixing them:
|
||||
![GIMP Procedure Browser][16]
|
||||
|
||||
Scheme is a Lisp-like language, so a major characteristic is that it uses a [prefix notation][17] and a [lot of parentheses][18]. Functions and operators are applied to a list of operands by prefixing them:
|
||||
|
||||
```
|
||||
(function-name operand operand ...)
|
||||
@ -67,7 +61,6 @@ Scheme is a Lisp-like language, so a major characteristic is that it uses a [pre
|
||||
|
||||
You can write your first script and save it to the **Scripts** folder found in the preferences window under **Folders → Scripts**. Mine is at `$HOME/.config/GIMP/2.10/scripts`. Write a file called `mandelbrot.scm` with:
|
||||
|
||||
|
||||
```
|
||||
; Complex numbers implementation
|
||||
(define (make-rectangular x y) (cons x y))
|
||||
@ -111,17 +104,17 @@ You can write your first script and save it to the **Scripts** folder found in t
|
||||
(define bytes-per-pixel (car (gimp-drawable-bpp drawable)))
|
||||
|
||||
; Fractal drawing section.
|
||||
; Code from: <https://rosettacode.org/wiki/Mandelbrot\_set\#Racket>
|
||||
; Code from: https://rosettacode.org/wiki/Mandelbrot_set#Racket
|
||||
(define (iterations a z i)
|
||||
(let ((z′ (add-c (mul-c z z) a)))
|
||||
(if (or (= i num-colors) (> (magnitude z′) threshold))
|
||||
(if (or (= i num-colors) (> (magnitude z′) threshold))
|
||||
i
|
||||
(iterations a z′ (+ i 1)))))
|
||||
|
||||
(define (iter->color i)
|
||||
(if (>= i num-colors)
|
||||
(list->vector '(0 0 0))
|
||||
(list->vector (vector-ref colors i))))
|
||||
(define (iter->color i)
|
||||
(if (>= i num-colors)
|
||||
(list->vector '(0 0 0))
|
||||
(list->vector (vector-ref colors i))))
|
||||
|
||||
(define z0 (make-rectangular 0 0))
|
||||
|
||||
@ -130,10 +123,10 @@ You can write your first script and save it to the **Scripts** folder found in t
|
||||
(real-y (- (* domain-height (/ y height)) offset-y))
|
||||
(a (make-rectangular real-x real-y))
|
||||
(i (iterations a z0 0))
|
||||
(color (iter->color i)))
|
||||
(cond ((and (< x end-x) (< y end-y)) (gimp-drawable-set-pixel drawable x y bytes-per-pixel color)
|
||||
(color (iter->color i)))
|
||||
(cond ((and (< x end-x) (< y end-y)) (gimp-drawable-set-pixel drawable x y bytes-per-pixel color)
|
||||
(loop (+ x 1) end-x y end-y))
|
||||
((and (>= x end-x) (< y end-y)) (gimp-progress-update (/ y end-y))
|
||||
((and (>= x end-x) (< y end-y)) (gimp-progress-update (/ y end-y))
|
||||
(loop 0 end-x (+ y 1) end-y)))))
|
||||
(loop 0 width 0 height)
|
||||
|
||||
@ -161,15 +154,14 @@ You can write your first script and save it to the **Scripts** folder found in t
|
||||
SF-ADJUSTMENT "X offset" '(2.25 -20 20 0.1 1 4 0)
|
||||
SF-ADJUSTMENT "Y offset" '(1.50 -20 20 0.1 1 4 0)
|
||||
)
|
||||
(script-fu-menu-register "script-fu-mandelbrot" "<Image>/Layer/")
|
||||
(script-fu-menu-register "script-fu-mandelbrot" "<Image>/Layer/")
|
||||
```
|
||||
|
||||
I will go through the script to show you what it does.
|
||||
|
||||
### Get ready to draw the fractal
|
||||
|
||||
Since this image is all about complex numbers, I wrote a quick and dirty implementation of complex numbers in Script-Fu. I defined the complex numbers as [pairs][18] of real numbers. Then I added the few functions needed for the script. I used [Racket's documentation][19] as inspiration for function names and roles:
|
||||
|
||||
Since this image is all about complex numbers, I wrote a quick and dirty implementation of complex numbers in Script-Fu. I defined the complex numbers as [pairs][19] of real numbers. Then I added the few functions needed for the script. I used [Racket's documentation][20] as inspiration for function names and roles:
|
||||
|
||||
```
|
||||
(define (make-rectangular x y) (cons x y))
|
||||
@ -198,7 +190,6 @@ Since this image is all about complex numbers, I wrote a quick and dirty impleme
|
||||
|
||||
The new function is called `script-fu-mandelbrot`. The best practice for writing a new function is to call it `script-fu-something` so that it can be identified in the Procedure Browser easily. The function requires a few parameters: an `image` to which it will add a layer with the fractal, the `palette-name` identifying the color palette to be used, the `threshold` value to stop the iteration, the `domain-width` and `domain-height` that identify the image boundaries, and the `offset-x` and `offset-y` to center the image to the desired feature. The script also needs some other parameters that it can deduce from the GIMP interface:
|
||||
|
||||
|
||||
```
|
||||
(define (script-fu-mandelbrot image palette-name threshold domain-width domain-height offset-x offset-y)
|
||||
(define num-colors (car (gimp-palette-get-info palette-name)))
|
||||
@ -212,7 +203,6 @@ The new function is called `script-fu-mandelbrot`. The best practice for writing
|
||||
|
||||
Then it creates a new layer and identifies it as the script's `drawable`. A "drawable" is the element you want to draw on:
|
||||
|
||||
|
||||
```
|
||||
(define new-layer (car (gimp-layer-new image
|
||||
width height
|
||||
@ -226,27 +216,25 @@ Then it creates a new layer and identifies it as the script's `drawable`. A "dra
|
||||
(define bytes-per-pixel (car (gimp-drawable-bpp drawable)))
|
||||
```
|
||||
|
||||
For the code determining the pixels' color, I used the [Racket][20] example on the [Rosetta Code][21] website. It is not the most optimized algorithm, but it is simple to understand. Even a non-mathematician like me can understand it. The `iterations` function determines how many steps the succession requires to pass the threshold value. To cap the iterations, I am using the number of colors in the palette. In other words, if the threshold is too high or the succession does not grow, the calculation stops at the `num-colors` value. The `iter->color` function transforms the number of iterations into a color using the provided palette. If the iteration number is equal to `num-colors`, it uses black because this means that the succession is probably bound and that pixel is in the Mandelbrot set:
|
||||
|
||||
For the code determining the pixels' color, I used the [Racket][21] example on the [Rosetta Code][22] website. It is not the most optimized algorithm, but it is simple to understand. Even a non-mathematician like me can understand it. The `iterations` function determines how many steps the succession requires to pass the threshold value. To cap the iterations, I am using the number of colors in the palette. In other words, if the threshold is too high or the succession does not grow, the calculation stops at the `num-colors` value. The `iter->color` function transforms the number of iterations into a color using the provided palette. If the iteration number is equal to `num-colors`, it uses black because this means that the succession is probably bound and that pixel is in the Mandelbrot set:
|
||||
|
||||
```
|
||||
; Fractal drawing section.
|
||||
; Code from: <https://rosettacode.org/wiki/Mandelbrot\_set\#Racket>
|
||||
; Code from: https://rosettacode.org/wiki/Mandelbrot_set#Racket
|
||||
(define (iterations a z i)
|
||||
(let ((z′ (add-c (mul-c z z) a)))
|
||||
(if (or (= i num-colors) (> (magnitude z′) threshold))
|
||||
(if (or (= i num-colors) (> (magnitude z′) threshold))
|
||||
i
|
||||
(iterations a z′ (+ i 1)))))
|
||||
|
||||
(define (iter->color i)
|
||||
(if (>= i num-colors)
|
||||
(list->vector '(0 0 0))
|
||||
(list->vector (vector-ref colors i))))
|
||||
(define (iter->color i)
|
||||
(if (>= i num-colors)
|
||||
(list->vector '(0 0 0))
|
||||
(list->vector (vector-ref colors i))))
|
||||
```
|
||||
|
||||
Because I have the feeling that Scheme users do not like to use loops, I implemented the function looping over the pixels as a recursive function. The `loop` function reads the starting coordinates and their upper boundaries. At each pixel, it defines some temporary variables with the `let*` function: `real-x` and `real-y` are the real coordinates of the pixel in the complex plane, according to the parameters; the `a` variable is the starting point for the succession; the `i` is the number of iterations; and finally `color` is the pixel color. Each pixel is colored with the `gimp-drawable-set-pixel` function that is an internal GIMP procedure. The peculiarity is that it is not undoable, and it does not trigger the image to refresh. Therefore, the image will not be updated during the operation. To play nice with the user, at the end of each row of pixels, it calls the `gimp-progress-update` function, which updates a progress bar in the user interface:
|
||||
|
||||
|
||||
```
|
||||
(define z0 (make-rectangular 0 0))
|
||||
|
||||
@ -255,17 +243,16 @@ Because I have the feeling that Scheme users do not like to use loops, I impleme
|
||||
(real-y (- (* domain-height (/ y height)) offset-y))
|
||||
(a (make-rectangular real-x real-y))
|
||||
(i (iterations a z0 0))
|
||||
(color (iter->color i)))
|
||||
(cond ((and (< x end-x) (< y end-y)) (gimp-drawable-set-pixel drawable x y bytes-per-pixel color)
|
||||
(color (iter->color i)))
|
||||
(cond ((and (< x end-x) (< y end-y)) (gimp-drawable-set-pixel drawable x y bytes-per-pixel color)
|
||||
(loop (+ x 1) end-x y end-y))
|
||||
((and (>= x end-x) (< y end-y)) (gimp-progress-update (/ y end-y))
|
||||
((and (>= x end-x) (< y end-y)) (gimp-progress-update (/ y end-y))
|
||||
(loop 0 end-x (+ y 1) end-y)))))
|
||||
(loop 0 width 0 height)
|
||||
```
|
||||
|
||||
At the calculation's end, the function needs to inform GIMP that it modified the `drawable`, and it should refresh the interface because the image is not "automagically" updated during the script's execution:
|
||||
|
||||
|
||||
```
|
||||
(gimp-drawable-update drawable 0 0 width height)
|
||||
(gimp-displays-flush)
|
||||
@ -275,7 +262,6 @@ At the calculation's end, the function needs to inform GIMP that it modified the
|
||||
|
||||
To use the `script-fu-mandelbrot` function in the graphical user interface (GUI), the script needs to inform GIMP. The `script-fu-register` function informs GIMP about the parameters required by the script and provides some documentation:
|
||||
|
||||
|
||||
```
|
||||
(script-fu-register
|
||||
"script-fu-mandelbrot" ; Function name
|
||||
@ -300,71 +286,69 @@ To use the `script-fu-mandelbrot` function in the graphical user interface (GUI)
|
||||
|
||||
Then the script tells GIMP to put the new function in the Layer menu with the label "Create a Mandelbrot layer":
|
||||
|
||||
|
||||
```
|
||||
`(script-fu-menu-register "script-fu-mandelbrot" "<Image>/Layer/")`
|
||||
(script-fu-menu-register "script-fu-mandelbrot" "<Image>/Layer/")
|
||||
```
|
||||
|
||||
Having registered the function, you can visualize it in the Procedure Browser.
|
||||
|
||||
![script-fu-mandelbrot function][22]
|
||||
|
||||
(Cristiano Fontana, [CC BY-SA 4.0][6])
|
||||
![script-fu-mandelbrot function][23]
|
||||
|
||||
### Run the script
|
||||
|
||||
Now that the function is ready and registered, you can draw the Mandelbrot fractal! First, create a square image and run the script from the Layers menu.
|
||||
|
||||
![script running][23]
|
||||
|
||||
(Cristiano Fontana, [CC BY-SA 4.0][6])
|
||||
![script running][24]
|
||||
|
||||
The default values are a good starting set to obtain the following image. The first time you run the script, create a very small image (e.g., 60x60 pixels) because this implementation is slow! It took several hours for my computer to create the following image in full 1920x1920 pixels. As I mentioned earlier, this is not the most optimized algorithm; rather, it was the easiest for me to understand.
|
||||
|
||||
![Mandelbrot set drawn using GIMP's Firecode palette][10]
|
||||
|
||||
Portion of a Mandelbrot fractal using GIMP's Firecode palette. (Cristiano Fontana, [CC BY-SA 4.0][6])
|
||||
![Mandelbrot set drawn using GIMP's Firecode palette][25]
|
||||
|
||||
### Learn more
|
||||
|
||||
This tutorial showed how to use GIMP's built-in scripting features to draw an image created with an algorithm. These images show GIMP's powerful set of tools that can be used for artistic applications and mathematical images.
|
||||
|
||||
If you want to move forward, I suggest you look at the official documentation and its [tutorial][14]. As an exercise, try modifying this script to draw a [Julia set][24], and please share the resulting image in the comments.
|
||||
If you want to move forward, I suggest you look at the official documentation and its [tutorial][26]. As an exercise, try modifying this script to draw a [Julia set][27], and please share the resulting image in the comments.
|
||||
|
||||
Image by: Rotated and magnified portion of the Mandelbrot set using Firecode. (Cristiano Fontana, CC BY-SA 4.0)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/2/gimp-mandelbrot
|
||||
|
||||
作者:[Cristiano L. Fontana][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/cristianofontana
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/painting_computer_screen_art_design_creative.png?itok=LVAeQx3_ (Painting art on a computer screen)
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://opensource.com/sites/default/files/lead-images/painting_computer_screen_art_design_creative.png
|
||||
[2]: https://www.gimp.org/
|
||||
[3]: https://en.wikipedia.org/wiki/Fractal
|
||||
[4]: https://en.wikipedia.org/wiki/Self-similarity
|
||||
[5]: https://opensource.com/sites/default/files/uploads/mandelbrot_portion.png (Portion of a Mandelbrot fractal using GIMPs Coldfire palette)
|
||||
[6]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[7]: https://docs.gimp.org/en/gimp-concepts-script-fu.html
|
||||
[8]: https://opensource.com/article/21/1/gimp-scripting
|
||||
[9]: https://en.wikipedia.org/wiki/Mandelbrot_set
|
||||
[10]: https://opensource.com/sites/default/files/uploads/mandelbrot.png (Mandelbrot set drawn using GIMP's Firecode palette)
|
||||
[11]: https://opensource.com/sites/default/files/uploads/mandelbrot_portion2.png (Rotated and magnified portion of the Mandelbrot set using Firecode.)
|
||||
[12]: https://en.wikipedia.org/wiki/Complex_number
|
||||
[5]: https://opensource.com/sites/default/files/uploads/mandelbrot_portion.png
|
||||
[6]: https://docs.gimp.org/en/gimp-concepts-script-fu.html
|
||||
[7]: https://opensource.com/article/21/1/gimp-scripting
|
||||
[8]: https://en.wikipedia.org/wiki/Mandelbrot_set
|
||||
[9]: https://opensource.com/sites/default/files/uploads/mandelbrot.png
|
||||
[10]: https://opensource.com/sites/default/files/uploads/mandelbrot_portion2.png
|
||||
[11]: https://en.wikipedia.org/wiki/Complex_number
|
||||
[12]: https://docs.gimp.org/en/gimp-concepts-script-fu.html
|
||||
[13]: https://en.wikipedia.org/wiki/Scheme_(programming_language)
|
||||
[14]: https://docs.gimp.org/en/gimp-using-script-fu-tutorial.html
|
||||
[15]: https://opensource.com/sites/default/files/uploads/procedure_browser_0.png (GIMP Procedure Browser)
|
||||
[16]: https://en.wikipedia.org/wiki/Polish_notation
|
||||
[17]: https://xkcd.com/297/
|
||||
[18]: https://www.gnu.org/software/guile/manual/html_node/Pairs.html
|
||||
[19]: https://docs.racket-lang.org/reference/generic-numbers.html?q=make-rectangular#%28part._.Complex_.Numbers%29
|
||||
[20]: https://racket-lang.org/
|
||||
[21]: https://rosettacode.org/wiki/Mandelbrot_set#Racket
|
||||
[22]: https://opensource.com/sites/default/files/uploads/mandelbrot_documentation.png (script-fu-mandelbrot function)
|
||||
[23]: https://opensource.com/sites/default/files/uploads/script_working.png (script running)
|
||||
[24]: https://en.wikipedia.org/wiki/Julia_set
|
||||
[15]: https://opensource.com/article/21/1/gimp-scripting
|
||||
[16]: https://opensource.com/sites/default/files/uploads/procedure_browser_0.png
|
||||
[17]: https://en.wikipedia.org/wiki/Polish_notation
|
||||
[18]: https://xkcd.com/297/
|
||||
[19]: https://www.gnu.org/software/guile/manual/html_node/Pairs.html
|
||||
[20]: https://docs.racket-lang.org/reference/generic-numbers.html?q=make-rectangular#%28part._.Complex_.Numbers%29
|
||||
[21]: https://racket-lang.org/
|
||||
[22]: https://rosettacode.org/wiki/Mandelbrot_set#Racket
|
||||
[23]: https://opensource.com/sites/default/files/uploads/mandelbrot_documentation.png
|
||||
[24]: https://opensource.com/sites/default/files/uploads/script_working.png
|
||||
[25]: https://opensource.com/sites/default/files/uploads/mandelbrot.png
|
||||
[26]: https://docs.gimp.org/en/gimp-using-script-fu-tutorial.html
|
||||
[27]: https://en.wikipedia.org/wiki/Julia_set
|
||||
|
@ -1,18 +1,20 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (A step-by-step guide to Knative eventing)
|
||||
[#]: via: (https://opensource.com/article/21/2/knative-eventing)
|
||||
[#]: author: (Jessica Cherry https://opensource.com/users/cherrybomb)
|
||||
[#]: subject: "A step-by-step guide to Knative eventing"
|
||||
[#]: via: "https://opensource.com/article/21/2/knative-eventing"
|
||||
[#]: author: "Jessica Cherry https://opensource.com/users/cherrybomb"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
A step-by-step guide to Knative eventing
|
||||
======
|
||||
Knative eventing is a way to create, send, and verify events in your
|
||||
cloud-native environment.
|
||||
Knative eventing is a way to create, send, and verify events in your cloud-native environment.
|
||||
|
||||
![Computer laptop in space][1]
|
||||
|
||||
Image by: Opensource.com
|
||||
|
||||
In a previous article, I covered [how to create a small app with Knative][2], which is an open source project that adds components to [Kubernetes][3] for deploying, running, and managing [serverless, cloud-native][4] applications. In this article, I'll explain Knative eventing, a way to create, send, and verify events in your cloud-native environment.
|
||||
|
||||
Events can be generated from many sources in your environment, and they can be confusing to manage or define. Since Knative follows the [CloudEvents][5] specification, it allows you to have one common abstraction point for your environment, where the events are defined to one specification.
|
||||
@ -25,7 +27,6 @@ This walkthrough uses [Minikube][7] with Kubernetes 1.19.0. It also makes some c
|
||||
|
||||
**Minikube pre-configuration commands:**
|
||||
|
||||
|
||||
```
|
||||
$ minikube config set kubernetes-version v1.19.0
|
||||
$ minikube config set memory 4000
|
||||
@ -34,7 +35,6 @@ $ minikube config set cpus 4
|
||||
|
||||
Before starting Minikube, run the following commands to make sure your configuration stays and start Minikube:
|
||||
|
||||
|
||||
```
|
||||
$ minikube delete
|
||||
$ minikube start
|
||||
@ -44,9 +44,8 @@ $ minikube start
|
||||
|
||||
Install the Knative eventing custom resource definitions (CRDs) using kubectl. The following shows the command and a snippet of the output:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl apply --filename <https://github.com/knative/eventing/releases/download/v0.20.0/eventing-crds.yaml>
|
||||
$ kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.20.0/eventing-crds.yaml
|
||||
|
||||
customresourcedefinition.apiextensions.k8s.io/apiserversources.sources.knative.dev created
|
||||
customresourcedefinition.apiextensions.k8s.io/brokers.eventing.knative.dev created
|
||||
@ -56,9 +55,8 @@ customresourcedefinition.apiextensions.k8s.io/triggers.eventing.knative.dev crea
|
||||
|
||||
Next, install the core components using kubectl:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl apply --filename <https://github.com/knative/eventing/releases/download/v0.20.0/eventing-core.yaml>
|
||||
$ kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.20.0/eventing-core.yaml
|
||||
namespace/knative-eventing created
|
||||
serviceaccount/eventing-controller created
|
||||
clusterrolebinding.rbac.authorization.k8s.io/eventing-controller created
|
||||
@ -66,23 +64,20 @@ clusterrolebinding.rbac.authorization.k8s.io/eventing-controller created
|
||||
|
||||
Since you're running a standalone version of the Knative eventing service, you must install the in-memory channel to pass events. Using kubectl, run:
|
||||
|
||||
|
||||
```
|
||||
`$ kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.20.0/in-memory-channel.yaml`
|
||||
$ kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.20.0/in-memory-channel.yaml
|
||||
```
|
||||
|
||||
Install the broker, which utilizes the channels and runs the event routing:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl apply --filename <https://github.com/knative/eventing/releases/download/v0.20.0/mt-channel-broker.yaml>
|
||||
$ kubectl apply --filename https://github.com/knative/eventing/releases/download/v0.20.0/mt-channel-broker.yaml
|
||||
clusterrole.rbac.authorization.k8s.io/knative-eventing-mt-channel-broker-controller created
|
||||
clusterrole.rbac.authorization.k8s.io/knative-eventing-mt-broker-filter created
|
||||
```
|
||||
|
||||
Next, create a namespace and add a small broker to it; this broker routes events to triggers. Create your namespace using kubectl:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl create namespace eventing-test
|
||||
namespace/eventing-test created
|
||||
@ -90,7 +85,6 @@ namespace/eventing-test created
|
||||
|
||||
Now create a small broker named `default` in your namespace. The following is the YAML from my **broker.yaml** file (which can be found in my GitHub repository):
|
||||
|
||||
|
||||
```
|
||||
apiVersion: eventing.knative.dev/v1
|
||||
kind: broker
|
||||
@ -101,7 +95,6 @@ metadata:
|
||||
|
||||
Then apply your broker file using kubectl:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl create -f broker.yaml
|
||||
broker.eventing.knative.dev/default created
|
||||
@ -109,11 +102,10 @@ $ kubectl create -f broker.yaml
|
||||
|
||||
Verify that everything is up and running (you should see the confirmation output) after you run the command:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test get broker default
|
||||
NAME URL AGE READY REASON
|
||||
default <http://broker-ingress.knative-eventing.svc.cluster.local/eventing-test/default> 3m6s True
|
||||
default http://broker-ingress.knative-eventing.svc.cluster.local/eventing-test/default 3m6s True
|
||||
```
|
||||
|
||||
You'll need this URL from the broker output later for sending events, so save it.
|
||||
@ -126,7 +118,6 @@ First, you need to create event consumers. You'll create two consumers in this w
|
||||
|
||||
**The hello-display YAML code:**
|
||||
|
||||
|
||||
```
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@ -135,7 +126,7 @@ metadata:
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: &labels
|
||||
matchLabels: &labels
|
||||
app: hello-display
|
||||
template:
|
||||
metadata:
|
||||
@ -145,7 +136,7 @@ spec:
|
||||
- name: event-display
|
||||
image: gcr.io/knative-releases/knative.dev/eventing-contrib/cmd/event_display
|
||||
|
||||
\---
|
||||
---
|
||||
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
@ -162,7 +153,6 @@ spec:
|
||||
|
||||
**The goodbye-display YAML code:**
|
||||
|
||||
|
||||
```
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@ -171,7 +161,7 @@ metadata:
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: &labels
|
||||
matchLabels: &labels
|
||||
app: goodbye-display
|
||||
template:
|
||||
metadata:
|
||||
@ -179,10 +169,10 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: event-display
|
||||
# Source code: <https://github.com/knative/eventing-contrib/tree/master/cmd/event\_display>
|
||||
# Source code: https://github.com/knative/eventing-contrib/tree/master/cmd/event_display
|
||||
image: gcr.io/knative-releases/knative.dev/eventing-contrib/cmd/event_display
|
||||
|
||||
\---
|
||||
---
|
||||
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
@ -199,7 +189,6 @@ spec:
|
||||
|
||||
The differences in the YAML between the two consumers are in the `app` and `metadata name` sections. While both consumers are on the same ports, you can target one when generating an event. Create the consumers using kubectl:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test apply -f hello-display.yaml
|
||||
deployment.apps/hello-display created
|
||||
@ -212,7 +201,6 @@ service/goodbye-display created
|
||||
|
||||
Check to make sure the deployments are running after you've applied the YAML files:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test get deployments hello-display goodbye-display
|
||||
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||
@ -226,7 +214,6 @@ Now, you need to create the triggers, which define the events the consumer recei
|
||||
|
||||
**The greeting-trigger.yaml code:**
|
||||
|
||||
|
||||
```
|
||||
apiVersion: eventing.knative.dev/v1
|
||||
kind: Trigger
|
||||
@ -246,7 +233,6 @@ spec:
|
||||
|
||||
To create the first trigger, apply your YAML file:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test apply -f greeting-trigger.yaml
|
||||
trigger.eventing.knative.dev/hello-display created
|
||||
@ -256,7 +242,6 @@ Next, make the second trigger using **sendoff-trigger.yaml**. This sends anythin
|
||||
|
||||
**The sendoff-trigger.yaml code:**
|
||||
|
||||
|
||||
```
|
||||
apiVersion: eventing.knative.dev/v1
|
||||
kind: Trigger
|
||||
@ -276,7 +261,6 @@ spec:
|
||||
|
||||
Next, apply your second trigger definition to the cluster:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test apply -f sendoff-trigger.yaml
|
||||
trigger.eventing.knative.dev/goodbye-display created
|
||||
@ -284,19 +268,17 @@ trigger.eventing.knative.dev/goodbye-display created
|
||||
|
||||
Confirm everything is correctly in place by getting your triggers from the cluster using kubectl:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test get triggers
|
||||
NAME BROKER SUBSCRIBER_URI AGE READY
|
||||
goodbye-display default <http://goodbye-display.eventing-test.svc.cluster.local/> 24s True
|
||||
hello-display default <http://hello-display.eventing-test.svc.cluster.local/> 46s True
|
||||
NAME BROKER SUBSCRIBER_URI AGE READY
|
||||
goodbye-display default http://goodbye-display.eventing-test.svc.cluster.local/ 24s True
|
||||
hello-display default http://hello-display.eventing-test.svc.cluster.local/ 46s True
|
||||
```
|
||||
|
||||
### Create an event producer
|
||||
|
||||
Create a pod you can use to send events. This is a simple pod deployment with curl and SSH access for you to [send events using curl][8]. Because the broker can be accessed only from inside the cluster where Knative eventing is installed, the pod needs to be in the cluster; this is the only way to send events into the cluster. Use the **event-producer.yaml** file with this code:
|
||||
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
@ -318,7 +300,6 @@ spec:
|
||||
|
||||
Next, deploy the pod by using kubectl:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test apply -f event-producer.yaml
|
||||
pod/curl created
|
||||
@ -326,7 +307,6 @@ pod/curl created
|
||||
|
||||
To verify, get the deployment and make sure the pod is up and running:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl get pods -n eventing-test
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
@ -339,14 +319,12 @@ Since this article has been so configuration-heavy, I imagine you'll be happy to
|
||||
|
||||
Begin by logging into the pod:
|
||||
|
||||
|
||||
```
|
||||
`$ kubectl -n eventing-test attach curl -it`
|
||||
$ kubectl -n eventing-test attach curl -it
|
||||
```
|
||||
|
||||
Once logged in, you'll see output similar to:
|
||||
|
||||
|
||||
```
|
||||
Defaulting container name to curl.
|
||||
Use 'kubectl describe pod/curl -n eventing-test' to see all of the containers in this pod.
|
||||
@ -356,9 +334,8 @@ If you don't see a command prompt, try pressing enter.
|
||||
|
||||
Now, generate an event using curl. This needs some extra definitions and requires the broker URL generated during the installation. This example sends a greeting to the broker:
|
||||
|
||||
|
||||
```
|
||||
curl -v "<http://broker-ingress.knative-eventing.svc.cluster.local/eventing-test/default>" \
|
||||
curl -v "http://broker-ingress.knative-eventing.svc.cluster.local/eventing-test/default" \
|
||||
-X POST \
|
||||
-H "Ce-Id: say-hello" \
|
||||
-H "Ce-Specversion: 1.0" \
|
||||
@ -372,31 +349,29 @@ curl -v "<http://broker-ingress.knative-eventing.svc.cluster.local/eventing-test
|
||||
|
||||
When you run the command, this should be the output (and you should receive a [202 Accepted][10] response):
|
||||
|
||||
|
||||
```
|
||||
> POST /eventing-test/default HTTP/1.1
|
||||
> User-Agent: curl/7.35.0
|
||||
> Host: broker-ingress.knative-eventing.svc.cluster.local
|
||||
> Accept: */*
|
||||
> Ce-Id: say-hello
|
||||
> Ce-Specversion: 1.0
|
||||
> Ce-Type: greeting
|
||||
> Ce-Source: not-sendoff
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 24
|
||||
>
|
||||
< HTTP/1.1 202 Accepted
|
||||
< Date: Sun, 24 Jan 2021 22:25:25 GMT
|
||||
< Content-Length: 0
|
||||
> POST /eventing-test/default HTTP/1.1
|
||||
> User-Agent: curl/7.35.0
|
||||
> Host: broker-ingress.knative-eventing.svc.cluster.local
|
||||
> Accept: */*
|
||||
> Ce-Id: say-hello
|
||||
> Ce-Specversion: 1.0
|
||||
> Ce-Type: greeting
|
||||
> Ce-Source: not-sendoff
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 24
|
||||
>
|
||||
< HTTP/1.1 202 Accepted
|
||||
< Date: Sun, 24 Jan 2021 22:25:25 GMT
|
||||
< Content-Length: 0
|
||||
```
|
||||
|
||||
The 202 means the trigger sent it to the **hello-display** consumer (because of the definition.)
|
||||
|
||||
Next, send a second definition to the **goodbye-display** consumer with this new curl command:
|
||||
|
||||
|
||||
```
|
||||
curl -v "<http://broker-ingress.knative-eventing.svc.cluster.local/eventing-test/default>" \
|
||||
curl -v "http://broker-ingress.knative-eventing.svc.cluster.local/eventing-test/default" \
|
||||
-X POST \
|
||||
-H "Ce-Id: say-goodbye" \
|
||||
-H "Ce-Specversion: 1.0" \
|
||||
@ -410,22 +385,21 @@ This time, it is a `sendoff` and not a greeting based on the previous setup sect
|
||||
|
||||
Your output should look like this, with another 202 returned:
|
||||
|
||||
|
||||
```
|
||||
> POST /eventing-test/default HTTP/1.1
|
||||
> User-Agent: curl/7.35.0
|
||||
> Host: broker-ingress.knative-eventing.svc.cluster.local
|
||||
> Accept: */*
|
||||
> Ce-Id: say-goodbye
|
||||
> Ce-Specversion: 1.0
|
||||
> Ce-Type: not-greeting
|
||||
> Ce-Source: sendoff
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 26
|
||||
>
|
||||
< HTTP/1.1 202 Accepted
|
||||
< Date: Sun, 24 Jan 2021 22:33:00 GMT
|
||||
< Content-Length: 0
|
||||
> POST /eventing-test/default HTTP/1.1
|
||||
> User-Agent: curl/7.35.0
|
||||
> Host: broker-ingress.knative-eventing.svc.cluster.local
|
||||
> Accept: */*
|
||||
> Ce-Id: say-goodbye
|
||||
> Ce-Specversion: 1.0
|
||||
> Ce-Type: not-greeting
|
||||
> Ce-Source: sendoff
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 26
|
||||
>
|
||||
< HTTP/1.1 202 Accepted
|
||||
< Date: Sun, 24 Jan 2021 22:33:00 GMT
|
||||
< Content-Length: 0
|
||||
```
|
||||
|
||||
Congratulations, you sent two events!
|
||||
@ -438,14 +412,12 @@ Now that the events have been sent, how do you know that the correct consumers r
|
||||
|
||||
Start with the **hello-display** consumer::
|
||||
|
||||
|
||||
```
|
||||
`$ kubectl -n eventing-test logs -l app=hello-display --tail=100`
|
||||
$ kubectl -n eventing-test logs -l app=hello-display --tail=100
|
||||
```
|
||||
|
||||
There isn't much running in this example cluster, so you should see only one event:
|
||||
|
||||
|
||||
```
|
||||
☁️ cloudevents.Event
|
||||
Validation: valid
|
||||
@ -467,7 +439,6 @@ You've confirmed the **hello-display** consumer received the event! Now check th
|
||||
|
||||
Start by running the same command but with **goodbye-display**:
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test logs -l app=goodbye-display --tail=100
|
||||
☁️ cloudevents.Event
|
||||
@ -494,9 +465,8 @@ So you sent events to each consumer using curl, but what if you want to send an
|
||||
|
||||
Here is a curl example of a definition for sending an event to both consumers:
|
||||
|
||||
|
||||
```
|
||||
curl -v "<http://broker-ingress.knative-eventing.svc.cluster.local/eventing-test/default>" \
|
||||
curl -v "http://broker-ingress.knative-eventing.svc.cluster.local/eventing-test/default" \
|
||||
-X POST \
|
||||
-H "Ce-Id: say-hello-goodbye" \
|
||||
-H "Ce-Specversion: 1.0" \
|
||||
@ -512,27 +482,25 @@ Here is sample output of what the events look like after they are sent.
|
||||
|
||||
**Output of the event being sent:**
|
||||
|
||||
|
||||
```
|
||||
> POST /eventing-test/default HTTP/1.1
|
||||
> User-Agent: curl/7.35.0
|
||||
> Host: broker-ingress.knative-eventing.svc.cluster.local
|
||||
> Accept: */*
|
||||
> Ce-Id: say-hello-goodbye
|
||||
> Ce-Specversion: 1.0
|
||||
> Ce-Type: greeting
|
||||
> Ce-Source: sendoff
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 41
|
||||
>
|
||||
< HTTP/1.1 202 Accepted
|
||||
< Date: Sun, 24 Jan 2021 23:04:15 GMT
|
||||
< Content-Length: 0
|
||||
> POST /eventing-test/default HTTP/1.1
|
||||
> User-Agent: curl/7.35.0
|
||||
> Host: broker-ingress.knative-eventing.svc.cluster.local
|
||||
> Accept: */*
|
||||
> Ce-Id: say-hello-goodbye
|
||||
> Ce-Specversion: 1.0
|
||||
> Ce-Type: greeting
|
||||
> Ce-Source: sendoff
|
||||
> Content-Type: application/json
|
||||
> Content-Length: 41
|
||||
>
|
||||
< HTTP/1.1 202 Accepted
|
||||
< Date: Sun, 24 Jan 2021 23:04:15 GMT
|
||||
< Content-Length: 0
|
||||
```
|
||||
|
||||
**Output of hello-display (showing two events):**
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test logs -l app=hello-display --tail=100
|
||||
☁️ cloudevents.Event
|
||||
@ -567,7 +535,6 @@ Data,
|
||||
|
||||
**Output of goodbye-display (also with two events):**
|
||||
|
||||
|
||||
```
|
||||
$ kubectl -n eventing-test logs -l app=goodbye-display --tail=100
|
||||
☁️ cloudevents.Event
|
||||
@ -611,15 +578,15 @@ Internal eventing in cloud events is pretty easy to track if it's going to a pre
|
||||
via: https://opensource.com/article/21/2/knative-eventing
|
||||
|
||||
作者:[Jessica Cherry][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/cherrybomb
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/computer_space_graphic_cosmic.png?itok=wu493YbB (Computer laptop in space)
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://opensource.com/sites/default/files/lead-images/computer_space_graphic_cosmic.png
|
||||
[2]: https://opensource.com/article/20/11/knative
|
||||
[3]: https://opensource.com/resources/what-is-kubernetes
|
||||
[4]: https://en.wikipedia.org/wiki/Cloud_native_computing
|
||||
|
@ -1,15 +1,16 @@
|
||||
[#]: subject: (4 steps to set up global modals in React)
|
||||
[#]: via: (https://opensource.com/article/21/5/global-modals-react)
|
||||
[#]: author: (Ajay Pratap https://opensource.com/users/ajaypratap)
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: "4 steps to set up global modals in React"
|
||||
[#]: via: "https://opensource.com/article/21/5/global-modals-react"
|
||||
[#]: author: "Ajay Pratap https://opensource.com/users/ajaypratap"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
4 steps to set up global modals in React
|
||||
======
|
||||
Learn how to create interactive pop-up windows in a React web app.
|
||||
|
||||
![Digital creative of a browser on the internet][1]
|
||||
|
||||
A modal dialog is a window that appears on top of a web page and requires a user's interaction before it disappears. [React][2] has a couple of ways to help you generate and manage modals with minimal coding.
|
||||
@ -24,11 +25,10 @@ In my opinion, the best way to manage modal dialogs in your React application is
|
||||
|
||||
Here are the steps (and code) to set up global modals in React. I'm using [Patternfly][3] as my foundation, but the principles apply to any project.
|
||||
|
||||
#### 1\. Create a global modal component
|
||||
#### 1. Create a global modal component
|
||||
|
||||
In a file called **GlobalModal.tsx**, create your modal definition:
|
||||
|
||||
|
||||
```
|
||||
import React, { useState, createContext, useContext } from 'react';
|
||||
import { CreateModal, DeleteModal,UpdateModal } from './components';
|
||||
@ -46,25 +46,25 @@ const MODAL_COMPONENTS: any = {
|
||||
};
|
||||
|
||||
type GlobalModalContext = {
|
||||
showModal: (modalType: string, modalProps?: any) => void;
|
||||
hideModal: () => void;
|
||||
showModal: (modalType: string, modalProps?: any) => void;
|
||||
hideModal: () => void;
|
||||
store: any;
|
||||
};
|
||||
|
||||
const initalState: GlobalModalContext = {
|
||||
showModal: () => {},
|
||||
hideModal: () => {},
|
||||
showModal: () => {},
|
||||
hideModal: () => {},
|
||||
store: {},
|
||||
};
|
||||
|
||||
const GlobalModalContext = createContext(initalState);
|
||||
export const useGlobalModalContext = () => useContext(GlobalModalContext);
|
||||
export const useGlobalModalContext = () => useContext(GlobalModalContext);
|
||||
|
||||
export const GlobalModal: React.FC<{}> = ({ children }) => {
|
||||
export const GlobalModal: React.FC<{}> = ({ children }) => {
|
||||
const [store, setStore] = useState();
|
||||
const { modalType, modalProps } = store || {};
|
||||
|
||||
const showModal = (modalType: string, modalProps: any = {}) => {
|
||||
const showModal = (modalType: string, modalProps: any = {}) => {
|
||||
setStore({
|
||||
...store,
|
||||
modalType,
|
||||
@ -72,7 +72,7 @@ export const GlobalModal: React.FC<{}> = ({ children }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const hideModal = () => {
|
||||
const hideModal = () => {
|
||||
setStore({
|
||||
...store,
|
||||
modalType: null,
|
||||
@ -80,19 +80,19 @@ export const GlobalModal: React.FC<{}> = ({ children }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const renderComponent = () => {
|
||||
const renderComponent = () => {
|
||||
const ModalComponent = MODAL_COMPONENTS[modalType];
|
||||
if (!modalType || !ModalComponent) {
|
||||
return null;
|
||||
}
|
||||
return <ModalComponent id="global-modal" {...modalProps} />;
|
||||
return <ModalComponent id="global-modal" {...modalProps} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<GlobalModalContext.Provider value={{ store, showModal, hideModal }}>
|
||||
<GlobalModalContext.Provider value={{ store, showModal, hideModal }}>
|
||||
{renderComponent()}
|
||||
{children}
|
||||
</GlobalModalContext.Provider>
|
||||
</GlobalModalContext.Provider>
|
||||
);
|
||||
};
|
||||
```
|
||||
@ -103,40 +103,39 @@ The `showModal` function takes two parameters: `modalType` and `modalProps`. The
|
||||
|
||||
The `hideModal` function doesn't have any parameters; calling it causes the current open modal to close.
|
||||
|
||||
#### 2\. Create modal dialog components
|
||||
#### 2. Create modal dialog components
|
||||
|
||||
In a file called **CreateModal.tsx**, create a modal:
|
||||
|
||||
|
||||
```
|
||||
import React from "react";
|
||||
import { Modal, ModalVariant, Button } from "@patternfly/react-core";
|
||||
import { useGlobalModalContext } from "../GlobalModal";
|
||||
|
||||
export const CreateModal = () => {
|
||||
export const CreateModal = () => {
|
||||
const { hideModal, store } = useGlobalModalContext();
|
||||
const { modalProps } = store || {};
|
||||
const { title, confirmBtn } = modalProps || {};
|
||||
|
||||
const handleModalToggle = () => {
|
||||
const handleModalToggle = () => {
|
||||
hideModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
<Modal
|
||||
variant={ModalVariant.medium}
|
||||
title={title || "Create Modal"}
|
||||
isOpen={true}
|
||||
onClose={handleModalToggle}
|
||||
actions={[
|
||||
<Button key="confirm" variant="primary" onClick={handleModalToggle}>
|
||||
<Button key="confirm" variant="primary" onClick={handleModalToggle}>
|
||||
{confirmBtn || "Confirm button"}
|
||||
</Button>,
|
||||
<Button key="cancel" variant="link" onClick={handleModalToggle}>
|
||||
</Button>,
|
||||
<Button key="cancel" variant="link" onClick={handleModalToggle}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
||||
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||
@ -144,7 +143,7 @@ export const CreateModal = () => {
|
||||
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
|
||||
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
|
||||
est laborum.
|
||||
</Modal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
```
|
||||
@ -153,34 +152,33 @@ This has a custom hook, `useGlobalModalContext`, that provides store object from
|
||||
|
||||
To delete a modal, create a file called **DeleteModal.tsx**:
|
||||
|
||||
|
||||
```
|
||||
import React from "react";
|
||||
import { Modal, ModalVariant, Button } from "@patternfly/react-core";
|
||||
import { useGlobalModalContext } from "../GlobalModal";
|
||||
|
||||
export const DeleteModal = () => {
|
||||
export const DeleteModal = () => {
|
||||
const { hideModal } = useGlobalModalContext();
|
||||
|
||||
const handleModalToggle = () => {
|
||||
const handleModalToggle = () => {
|
||||
hideModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
<Modal
|
||||
variant={ModalVariant.medium}
|
||||
title="Delete Modal"
|
||||
isOpen={true}
|
||||
onClose={handleModalToggle}
|
||||
actions={[
|
||||
<Button key="confirm" variant="primary" onClick={handleModalToggle}>
|
||||
<Button key="confirm" variant="primary" onClick={handleModalToggle}>
|
||||
Confirm
|
||||
</Button>,
|
||||
<Button key="cancel" variant="link" onClick={handleModalToggle}>
|
||||
</Button>,
|
||||
<Button key="cancel" variant="link" onClick={handleModalToggle}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
||||
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||
@ -188,41 +186,40 @@ export const DeleteModal = () => {
|
||||
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
|
||||
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
|
||||
est laborum.
|
||||
</Modal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
To update a modal, create a file called **UpdateModal.tsx** and add this code:
|
||||
|
||||
|
||||
```
|
||||
import React from "react";
|
||||
import { Modal, ModalVariant, Button } from "@patternfly/react-core";
|
||||
import { useGlobalModalContext } from "../GlobalModal";
|
||||
|
||||
export const UpdateModal = () => {
|
||||
export const UpdateModal = () => {
|
||||
const { hideModal } = useGlobalModalContext();
|
||||
|
||||
const handleModalToggle = () => {
|
||||
const handleModalToggle = () => {
|
||||
hideModal();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
<Modal
|
||||
variant={ModalVariant.medium}
|
||||
title="Update Modal"
|
||||
isOpen={true}
|
||||
onClose={handleModalToggle}
|
||||
actions={[
|
||||
<Button key="confirm" variant="primary" onClick={handleModalToggle}>
|
||||
<Button key="confirm" variant="primary" onClick={handleModalToggle}>
|
||||
Confirm
|
||||
</Button>,
|
||||
<Button key="cancel" variant="link" onClick={handleModalToggle}>
|
||||
</Button>,
|
||||
<Button key="cancel" variant="link" onClick={handleModalToggle}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
|
||||
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
|
||||
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
||||
@ -230,15 +227,14 @@ export const UpdateModal = () => {
|
||||
velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
|
||||
cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
|
||||
est laborum.
|
||||
</Modal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### 3\. Integrate GlobalModal into the top-level component in your application
|
||||
|
||||
To integrate the new modal structure you've created into your app, you just import the global modal class you've created. Here's my sample **App.tsx** file:
|
||||
#### 3. Integrate GlobalModal into the top-level component in your application
|
||||
|
||||
To integrate the new modal structure you've created into your app, you just import the global modal class you've created. Here's my sample **App.tsx**file:
|
||||
|
||||
```
|
||||
import "@patternfly/react-core/dist/styles/base.css";
|
||||
@ -248,9 +244,9 @@ import { AppLayout } from "./AppLayout";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<GlobalModal>
|
||||
<AppLayout />
|
||||
</GlobalModal>
|
||||
<GlobalModal>
|
||||
<AppLayout />
|
||||
</GlobalModal>
|
||||
);
|
||||
}
|
||||
```
|
||||
@ -259,55 +255,54 @@ App.tsx is the top-level component in your app, but you can add another componen
|
||||
|
||||
`GlobalModal` is the root-level component where all your modal components are imported and mapped with their specific `modalType`.
|
||||
|
||||
#### 4\. Select the modal's button from the AppLayout component
|
||||
#### 4. Select the modal's button from the AppLayout component
|
||||
|
||||
Adding a button to your modal with **AppLayout.js**:
|
||||
|
||||
|
||||
```
|
||||
import React from "react";
|
||||
import { Button, ButtonVariant } from "@patternfly/react-core";
|
||||
import { useGlobalModalContext, MODAL_TYPES } from "./components/GlobalModal";
|
||||
|
||||
export const AppLayout = () => {
|
||||
export const AppLayout = () => {
|
||||
const { showModal } = useGlobalModalContext();
|
||||
|
||||
const createModal = () => {
|
||||
const createModal = () => {
|
||||
showModal(MODAL_TYPES.CREATE_MODAL, {
|
||||
title: "Create instance form",
|
||||
confirmBtn: "Save"
|
||||
});
|
||||
};
|
||||
|
||||
const deleteModal = () => {
|
||||
const deleteModal = () => {
|
||||
showModal(MODAL_TYPES.DELETE_MODAL);
|
||||
};
|
||||
|
||||
const updateModal = () => {
|
||||
const updateModal = () => {
|
||||
showModal(MODAL_TYPES.UPDATE_MODAL);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant={ButtonVariant.primary} onClick={createModal}>
|
||||
<>
|
||||
<Button variant={ButtonVariant.primary} onClick={createModal}>
|
||||
Create Modal
|
||||
</Button>
|
||||
<br />
|
||||
<br />
|
||||
<Button variant={ButtonVariant.primary} onClick={deleteModal}>
|
||||
</Button>
|
||||
<br />
|
||||
<br />
|
||||
<Button variant={ButtonVariant.primary} onClick={deleteModal}>
|
||||
Delete Modal
|
||||
</Button>
|
||||
<br />
|
||||
<br />
|
||||
<Button variant={ButtonVariant.primary} onClick={updateModal}>
|
||||
</Button>
|
||||
<br />
|
||||
<br />
|
||||
<Button variant={ButtonVariant.primary} onClick={updateModal}>
|
||||
Update Modal
|
||||
</Button>
|
||||
</>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
There are three buttons in the AppLayout component: create modal, delete modal, and update modal. Each modal is mapped with the corresponding `modalType`: `CREATE_MODAL`, `DELETE_MODAL`, or `UPDATE_MODAL`.
|
||||
There are three buttons in the AppLayout component: create modal, delete modal, and update modal. Each modal is mapped with the corresponding `modalType` : `CREATE_MODAL`, `DELETE_MODAL`, or `UPDATE_MODAL`.
|
||||
|
||||
### Use global dialogs
|
||||
|
||||
@ -315,22 +310,20 @@ Global modals are a clean and efficient way to handle dialogs in React. They are
|
||||
|
||||
If you'd like to see the code in action, I've included the [complete application][4] I created for this article in a sandbox.
|
||||
|
||||
Leslie Hinson sits down with Andrés Galante, an expert HTML and CSS coder who travels the world...
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/5/global-modals-react
|
||||
|
||||
作者:[Ajay Pratap][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/ajaypratap
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/browser_web_internet_website.png?itok=g5B_Bw62 (Digital creative of a browser on the internet)
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://opensource.com/sites/default/files/lead-images/browser_web_internet_website.png
|
||||
[2]: https://reactjs.org/
|
||||
[3]: https://www.patternfly.org/v4/
|
||||
[4]: https://codesandbox.io/s/affectionate-pine-gib74
|
||||
|
@ -1,18 +1,20 @@
|
||||
[#]: subject: (Get started with Java serverless functions)
|
||||
[#]: via: (https://opensource.com/article/21/6/java-serverless-functions)
|
||||
[#]: author: (Daniel Oh https://opensource.com/users/daniel-oh)
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: "Get started with Java serverless functions"
|
||||
[#]: via: "https://opensource.com/article/21/6/java-serverless-functions"
|
||||
[#]: author: "Daniel Oh https://opensource.com/users/daniel-oh"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
Get started with Java serverless functions
|
||||
======
|
||||
Quarkus allows you to develop serverless workloads with familiar Java
|
||||
technology.
|
||||
Quarkus allows you to develop serverless workloads with familiar Java technology.
|
||||
|
||||
![Tips and gears turning][1]
|
||||
|
||||
Image by: opensource.com
|
||||
|
||||
The [serverless Java][2] journey started out with functions—small snippets of code running on demand. This phase didn't last long. Although functions based on virtual machine architecture in the 1.0 phase made this paradigm very popular, as the graphic below shows, there were limits around execution time, protocols, and poor local-development experience.
|
||||
|
||||
Developers then realized that they could apply the same serverless traits and benefits to microservices and Linux containers. This launched the 1.5 phase, where some serverless containers completely abstracted [Kubernetes][3], delivering the serverless experience through [Knative][4] or another abstraction layer that sits on top of it.
|
||||
@ -21,24 +23,21 @@ In the 2.0 phase, serverless starts to handle more complex orchestration and int
|
||||
|
||||
![The serverless Java journey][5]
|
||||
|
||||
(Daniel Oh, [CC BY-SA 4.0][6])
|
||||
|
||||
Before Java developers can start developing new serverless functions, their first task is to choose a new cloud-native Java framework that allows them to run Java functions quicker with a smaller memory footprint than traditional monolithic applications. This can be applied to various infrastructure environments, from physical servers to virtual machines to containers in multi- and hybrid-cloud environments.
|
||||
|
||||
Developers might consider an opinionated Spring framework that uses the `java.util.function` package in [Spring Cloud Function][7] to support the development of imperative and reactive functions. Spring also enables developers to deploy Java functions to installable serverless platforms such as [Kubeless][8], [Apache OpenWhisk][9], [Fission][10], and [Project Riff][11]. However, there are concerns about slow startup and response times and heavy memory-consuming processes with Spring. This problem can be worse when running Java functions on scalable container environments such as Kubernetes.
|
||||
Developers might consider an opinionated Spring framework that uses the `java.util.function` package in [Spring Cloud Function][6] to support the development of imperative and reactive functions. Spring also enables developers to deploy Java functions to installable serverless platforms such as [Kubeless][7], [Apache OpenWhisk][8], [Fission][9], and [Project Riff][10]. However, there are concerns about slow startup and response times and heavy memory-consuming processes with Spring. This problem can be worse when running Java functions on scalable container environments such as Kubernetes.
|
||||
|
||||
[Quarkus][12] is a new open source cloud-native Java framework that can help solve these problems. It aims to design serverless applications and write cloud-native microservices for running on cloud infrastructures (e.g., Kubernetes).
|
||||
[Quarkus][11] is a new open source cloud-native Java framework that can help solve these problems. It aims to design serverless applications and write cloud-native microservices for running on cloud infrastructures (e.g., Kubernetes).
|
||||
|
||||
Quarkus rethinks Java, using a closed-world approach to building and running it. It has turned Java into a runtime that's comparable to Go. Quarkus also includes more than 100 extensions that integrate enterprise capabilities, including database access, serverless integration, messaging, security, observability, and business automation.
|
||||
|
||||
Here is a quick example of how developers can scaffold a Java serverless function project with Quarkus.
|
||||
|
||||
### 1\. Create a Quarkus serverless Maven project
|
||||
### 1. Create a Quarkus serverless Maven project
|
||||
|
||||
Developers have multiple options to install a local Kubernetes cluster, including [Minikube][13] and [OKD][14] (OpenShift Kubernetes Distribution). This tutorial uses an OKD cluster for a developer's local environment because of the easy setup of serverless functionality on Knative and DevOps toolings. These guides for [OKD installation][15] and [Knative operator installation][16] offer more information about setting them up.
|
||||
|
||||
The following command generates a Quarkus project (e.g., `quarkus-serverless-restapi`) to expose a simple REST API and download a `quarkus-openshift` extension for Knative service deployment:
|
||||
Developers have multiple options to install a local Kubernetes cluster, including [Minikube][12] and [OKD][13] (OpenShift Kubernetes Distribution). This tutorial uses an OKD cluster for a developer's local environment because of the easy setup of serverless functionality on Knative and DevOps toolings. These guides for [OKD installation][14] and [Knative operator installation][15] offer more information about setting them up.
|
||||
|
||||
The following command generates a Quarkus project (e.g., `quarkus-serverless-restapi` ) to expose a simple REST API and download a `quarkus-openshift` extension for Knative service deployment:
|
||||
|
||||
```
|
||||
$ mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create \
|
||||
@ -48,50 +47,45 @@ $ mvn io.quarkus:quarkus-maven-plugin:1.13.4.Final:create \
|
||||
-DclassName="org.acme.getting.started.GreetingResource"
|
||||
```
|
||||
|
||||
### 2\. Run serverless functions locally
|
||||
### 2. Run serverless functions locally
|
||||
|
||||
Run the application using Quarkus development mode to check if the REST API works, then tweak the code a bit:
|
||||
|
||||
|
||||
```
|
||||
`$ ./mvnw quarkus:dev`
|
||||
$ ./mvnw quarkus:dev
|
||||
```
|
||||
|
||||
The output will look like this:
|
||||
|
||||
|
||||
```
|
||||
__ ____ __ _____ ___ __ ____ ______
|
||||
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
|
||||
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
|
||||
\--\\___\\_\\____/_/ |_/_/|_/_/|_|\\____/___/
|
||||
INFO [io.quarkus] (Quarkus Main Thread) quarkus-serverless-restapi 1.0.0-SNAPSHOT on JVM (powered by Quarkus xx.xx.xx.) started in 2.386s. Listening on: <http://localhost:8080>
|
||||
__ ____ __ _____ ___ __ ____ ______
|
||||
--/ __ \/ / / / _ | / _ \/ //_/ / / / __/
|
||||
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \
|
||||
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/
|
||||
INFO [io.quarkus] (Quarkus Main Thread) quarkus-serverless-restapi 1.0.0-SNAPSHOT on JVM (powered by Quarkus xx.xx.xx.) started in 2.386s. Listening on: http://localhost:8080
|
||||
INFO [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
|
||||
INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, kubernetes, resteasy]
|
||||
```
|
||||
|
||||
> **Note**: Keep your Quarkus application running to use Live Coding. This allows you to avoid having to rebuild, redeploy the application, and restart the runtime whenever the code changes.
|
||||
|
||||
Now you can hit the REST API with a quick `curl` command. The output should be `Hello RESTEasy`:
|
||||
|
||||
Now you can hit the REST API with a quick `curl` command. The output should be `Hello RESTEasy` :
|
||||
|
||||
```
|
||||
$ curl localhost:8080/hello
|
||||
Hello RESTEasy
|
||||
```
|
||||
|
||||
Tweak the return text in `GreetingResource.java`:
|
||||
|
||||
Tweak the return text in `GreetingResource.java` :
|
||||
|
||||
```
|
||||
public [String][17] hello() {
|
||||
public String hello() {
|
||||
return "Quarkus Function on Kubernetes";
|
||||
}
|
||||
```
|
||||
|
||||
You will see new output when you reinvoke the REST API:
|
||||
|
||||
|
||||
```
|
||||
$ curl localhost:8080/hello
|
||||
Quarkus Function on Kubernetes
|
||||
@ -99,87 +93,77 @@ Quarkus Function on Kubernetes
|
||||
|
||||
There's not been a big difference between normal microservices and serverless functions. A benefit of Quarkus is that it enables developers to use any microservice to deploy Kubernetes as a serverless function.
|
||||
|
||||
### 3\. Deploy the functions to a Knative service
|
||||
### 3. Deploy the functions to a Knative service
|
||||
|
||||
If you haven't already, [create a namespace][18] (e.g., `quarkus-serverless-restapi`) on your OKD (Kubernetes) cluster to deploy this Java serverless function.
|
||||
|
||||
Quarkus enables developers to generate Knative and Kubernetes resources by adding the following variables in `src/main/resources/application.properties`:
|
||||
If you haven't already, [create a namespace][16] (e.g., `quarkus-serverless-restapi` ) on your OKD (Kubernetes) cluster to deploy this Java serverless function.
|
||||
|
||||
Quarkus enables developers to generate Knative and Kubernetes resources by adding the following variables in `src/main/resources/application.properties` :
|
||||
|
||||
```
|
||||
quarkus.container-image.group=quarkus-serverless-restapi <1>
|
||||
quarkus.container-image.registry=image-registry.openshift-image-registry.svc:5000 <2>
|
||||
quarkus.kubernetes-client.trust-certs=true <3>
|
||||
quarkus.kubernetes.deployment-target=knative <4>
|
||||
quarkus.kubernetes.deploy=true <5>
|
||||
quarkus.openshift.build-strategy=docker <6>
|
||||
quarkus.container-image.group=quarkus-serverless-restapi <1>
|
||||
quarkus.container-image.registry=image-registry.openshift-image-registry.svc:5000 <2>
|
||||
quarkus.kubernetes-client.trust-certs=true <3>
|
||||
quarkus.kubernetes.deployment-target=knative <4>
|
||||
quarkus.kubernetes.deploy=true <5>
|
||||
quarkus.openshift.build-strategy=docker <6>
|
||||
```
|
||||
|
||||
> Legend:
|
||||
>
|
||||
> <1> Define a project name where you deploy a serverless application
|
||||
> <2> The container registry to use
|
||||
> <3> Use self-signed certs in this simple example to trust them
|
||||
> <4> Enable the generation of Knative resources
|
||||
> <5> Instruct the extension to deploy to OpenShift after the container image is built
|
||||
> <6> Set the Docker build strategy
|
||||
|
||||
> <1> Define a project name where you deploy a serverless application
|
||||
<2> The container registry to use
|
||||
<3> Use self-signed certs in this simple example to trust them
|
||||
<4> Enable the generation of Knative resources
|
||||
<5> Instruct the extension to deploy to OpenShift after the container image is built
|
||||
<6> Set the Docker build strategy
|
||||
|
||||
This command builds the application then deploys it directly to the OKD cluster:
|
||||
|
||||
|
||||
```
|
||||
`$ ./mvnw clean package -DskipTests`
|
||||
$ ./mvnw clean package -DskipTests
|
||||
```
|
||||
|
||||
> **Note:** Make sure to log in to the right project (e.g., `quarkus-serverless-restapi`) by using the `oc login` command ahead of time.
|
||||
> **Note:** Make sure to log in to the right project (e.g., `quarkus-serverless-restapi` ) by using the `oc login` command ahead of time.
|
||||
|
||||
The output should end with `BUILD SUCCESS`.
|
||||
|
||||
Add a Quarkus label to the Knative service with this `oc` command:
|
||||
|
||||
|
||||
```
|
||||
$ oc label rev/quarkus-serverless-restapi-00001
|
||||
$ oc label rev/quarkus-serverless-restapi-00001
|
||||
app.openshift.io/runtime=quarkus --overwrite
|
||||
```
|
||||
|
||||
Then access the OKD web console to go to the [Topology view in the Developer perspective][19]. You might see that your pod (serverless function) is already scaled down to zero (white-line circle).
|
||||
Then access the OKD web console to go to the [Topology view in the Developer perspective][17]. You might see that your pod (serverless function) is already scaled down to zero (white-line circle).
|
||||
|
||||
![Topology view][20]
|
||||
![Topology view][18]
|
||||
|
||||
(Daniel Oh, [CC BY-SA 4.0][6])
|
||||
|
||||
### 4\. Test the functions on Kubernetes
|
||||
### 4. Test the functions on Kubernetes
|
||||
|
||||
Retrieve a route `URL` of the serverless function by running the following `oc` command:
|
||||
|
||||
|
||||
```
|
||||
$ oc get rt/quarkus-serverless-restapi
|
||||
[...]
|
||||
NAME URL READY REASON
|
||||
quarkus-serverless[...] <http://quarkus\[...\].SUBDOMAIN> True
|
||||
quarkus-serverless[...] http://quarkus[...].SUBDOMAIN True
|
||||
```
|
||||
|
||||
Access the route `URL` with a `curl` command:
|
||||
|
||||
|
||||
```
|
||||
`$ curl http://quarkus-serverless-restapi-quarkus-serverless-restapi.SUBDOMAIN/hello`
|
||||
$ curl http://quarkus-serverless-restapi-quarkus-serverless-restapi.SUBDOMAIN/hello
|
||||
```
|
||||
|
||||
In a few seconds, you will get the same result as you got locally:
|
||||
|
||||
|
||||
```
|
||||
`Quarkus Function on Kubernetes`
|
||||
Quarkus Function on Kubernetes
|
||||
```
|
||||
|
||||
When you return to the Topology view in the OKD cluster, the Knative service scales up automatically.
|
||||
|
||||
![Scaling the Knative Function][21]
|
||||
|
||||
(Daniel Oh, [CC BY-SA 4.0][6])
|
||||
![Scaling the Knative Function][19]
|
||||
|
||||
This Knative service pod will go down to zero again in 30 seconds because of Knative serving's default setting.
|
||||
|
||||
@ -189,37 +173,37 @@ The serverless journey has evolved, starting with functions on virtual machines
|
||||
|
||||
The next article in this series will guide you on optimizing Java serverless functions in Kubernetes for faster startup time and small memory footprints at scale.
|
||||
|
||||
Image by: (Daniel Oh, CC BY-SA 4.0)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/21/6/java-serverless-functions
|
||||
|
||||
作者:[Daniel Oh][a]
|
||||
选题:[lujun9972][b]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/daniel-oh
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/gears_devops_learn_troubleshooting_lightbulb_tips_520.png?itok=HcN38NOk (Tips and gears turning)
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://opensource.com/sites/default/files/lead-images/gears_devops_learn_troubleshooting_lightbulb_tips_520.png
|
||||
[2]: https://opensource.com/article/21/5/what-serverless-java
|
||||
[3]: https://opensource.com/article/19/6/reasons-kubernetes
|
||||
[4]: https://cloud.google.com/knative/
|
||||
[5]: https://opensource.com/sites/default/files/uploads/serverless-journey.png (The serverless Java journey)
|
||||
[6]: https://creativecommons.org/licenses/by-sa/4.0/
|
||||
[7]: https://spring.io/serverless
|
||||
[8]: https://kubeless.io/
|
||||
[9]: https://openwhisk.apache.org/
|
||||
[10]: https://fission.io/
|
||||
[11]: https://projectriff.io/
|
||||
[12]: https://quarkus.io/
|
||||
[13]: https://minikube.sigs.k8s.io/docs/start/
|
||||
[14]: https://docs.okd.io/latest/welcome/index.html
|
||||
[15]: https://docs.okd.io/latest/installing/index.html
|
||||
[16]: https://knative.dev/docs/install/knative-with-operators/
|
||||
[17]: http://www.google.com/search?hl=en&q=allinurl%3Adocs.oracle.com+javase+docs+api+string
|
||||
[18]: https://docs.okd.io/latest/applications/projects/configuring-project-creation.html
|
||||
[19]: https://docs.okd.io/latest/applications/application_life_cycle_management/odc-viewing-application-composition-using-topology-view.html
|
||||
[20]: https://opensource.com/sites/default/files/uploads/topologyview.png (Topology view)
|
||||
[21]: https://opensource.com/sites/default/files/uploads/scale-up-knative-function.png (Scaling the Knative Function)
|
||||
[5]: https://opensource.com/sites/default/files/uploads/serverless-journey.png
|
||||
[6]: https://spring.io/serverless
|
||||
[7]: https://kubeless.io/
|
||||
[8]: https://openwhisk.apache.org/
|
||||
[9]: https://fission.io/
|
||||
[10]: https://projectriff.io/
|
||||
[11]: https://quarkus.io/
|
||||
[12]: https://minikube.sigs.k8s.io/docs/start/
|
||||
[13]: https://docs.okd.io/latest/welcome/index.html
|
||||
[14]: https://docs.okd.io/latest/installing/index.html
|
||||
[15]: https://knative.dev/docs/install/knative-with-operators/
|
||||
[16]: https://docs.okd.io/latest/applications/projects/configuring-project-creation.html
|
||||
[17]: https://docs.okd.io/latest/applications/application_life_cycle_management/odc-viewing-application-composition-using-topology-view.html
|
||||
[18]: https://opensource.com/sites/default/files/uploads/topologyview.png
|
||||
[19]: https://opensource.com/sites/default/files/uploads/scale-up-knative-function.png
|
||||
|
@ -0,0 +1,253 @@
|
||||
[#]: subject: "The Definitive Guide to Using and Customizing the Dock in Ubuntu"
|
||||
[#]: via: "https://itsfoss.com/customize-ubuntu-dock/"
|
||||
[#]: author: "Abhishek Prakash https://itsfoss.com/author/abhishek/"
|
||||
[#]: collector: "lkxed"
|
||||
[#]: translator: " "
|
||||
[#]: reviewer: " "
|
||||
[#]: publisher: " "
|
||||
[#]: url: " "
|
||||
|
||||
The Definitive Guide to Using and Customizing the Dock in Ubuntu
|
||||
======
|
||||
When you log into Ubuntu, you’ll see the dock on the left side with some application icons on it. This dock (also known as launcher or sometimes as panel) allows you to quickly launch your frequently used programs.
|
||||
|
||||
![Ubuntu Dock][1]
|
||||
|
||||
I rely heavily on the dock and I am going to share a few tips about using the dock effectively and customize its looks and position.
|
||||
|
||||
You’ll learn the following in this tutorial:
|
||||
|
||||
* Basic usage of the dock: adding more applications and using shortcuts for launching applications.
|
||||
* Customize the looks of the dock: Change the icon size, icon positions.
|
||||
* Change the position: for single screen and multi-monitor setup
|
||||
* Hide mounted disk from the dock
|
||||
* Auto-hide or disable the dock
|
||||
* Possibility of additional dock customization with dconf-editor
|
||||
* Replace dock with other docking applications
|
||||
|
||||
I’ll use the terms dock, panel and launcher in the tutorial. All of them refer to the same thing.
|
||||
|
||||
### Using the Ubuntu dock: Absolute basic that you must know
|
||||
|
||||
If you are new to Ubuntu, you should know a few things about using the dock. You’ll eventually discover these dock features, I’ll just speed up the discovery process for you.
|
||||
|
||||
![A Video from YouTube][2]
|
||||
|
||||
[Subscribe to our YouTube channel for more Linux videos][3]
|
||||
|
||||
#### Add new applications to the dock (or remove them)
|
||||
|
||||
The steps are simple. Search for the application from the menu and run it.
|
||||
|
||||
The running application appears in the dock, below all other icons. Right click on it and select the “Add to Favorites” option. This will lock the icon to the dock.
|
||||
|
||||
![Right click on the icon and select "Add to Favorites" to add icons to the dock in Ubuntu][4]
|
||||
|
||||
Removing an app icon from the doc is even easier. You don’t even need to run the application. Simply right click on it and select “Remove From Favorites”.
|
||||
|
||||
![Right-click on the icon and select "Remove from Favorites" to remove icons from the dock in Ubuntu][5]
|
||||
|
||||
#### Reorder icon position
|
||||
|
||||
By default, new application icons are added after all the other icons on the launcher. You don’t have to live with it as it is.
|
||||
|
||||
To change the order of the icons, you just need to drag and drop to the other position of your choice. No need to “lock it” or any additional effort. It stays on that location until you make some changes again.
|
||||
|
||||
![Reorder Icons On Ubuntu Docks][6]
|
||||
|
||||
#### Right click to get additional options for some apps
|
||||
|
||||
Left-clicking on an icon launches the application or bring it to focus if the application is already running.
|
||||
|
||||
Right-clicking the icon gives you additional options. Different applications will have different options.
|
||||
|
||||
For browsers, you can open a new private window or preview all the running windows.
|
||||
|
||||
![Right Click Icons Ubuntu Dock][7]
|
||||
|
||||
For file manager, you can go to all the bookmarked directories or preview opened windows.
|
||||
|
||||
You can, of course, quit the application. Most applications will quit while some applications like Telegram will be minimized to the system tray.
|
||||
|
||||
#### Use keyboard shortcut to launch applications quickly [Not many people know about this one]
|
||||
|
||||
The dock allows you to launch an application in a single mouse click. But if you are like me, you can save that mouse click with a keyboard shortcut.
|
||||
|
||||
Using the Super/Window key and a number key will launch the application on that position.
|
||||
|
||||
![Keyboard Shortcut For Ubuntu Dock][8]
|
||||
|
||||
If the application is already running, it is brought to focus, i.e. it appears in front of all the other running application windows.
|
||||
|
||||
Since it is position-based, you should make sure that you don’t reorder the icons all the time. Personally, I keep Firefox at position 1, file manager at 2 and the alternate browser at 3 and so on until number 9. This way, I quickly launch the file manager with Super+2.
|
||||
|
||||
I find it easier specially because I have a three screen setup and moving the mouse to the launcher on the first screen is a bit too much of trouble. You can enable or disable the dock on additional screen. I’ll show that to you later in this tutorial.
|
||||
|
||||
### Change the position of the dock
|
||||
|
||||
By default, the dock is located on the left side of your screen. Some people like the launcher at the bottom, in a more traditional way.
|
||||
|
||||
[Ubuntu allows you to change the position of the dock][9]. You can move it to the bottom or to the right side. I am not sure many people actually put the dock on the top, so moving the dock to the top is not an option here.
|
||||
|
||||
![Change Launcher Position in Ubuntu][10]
|
||||
|
||||
To change the dock position, go to Settings->Appearance. You should see some options under Dock section. You need to change the “Position on screen” settings here.
|
||||
|
||||
![Change Dock Position in Ubuntu][11]
|
||||
|
||||
#### Position of dock on a multiple monitor setup
|
||||
|
||||
If you have multiple screens attached to your system, you can choose whether to display the dock on all screens or one of the chosen screens.
|
||||
|
||||
![Ubuntu Dock Settings Multimonitor][12]
|
||||
|
||||
Personally, I display the dock on my laptop screen only which is my main screen. This gives me maximum space on the additional two screens.
|
||||
|
||||
### Change the appearance of the dock
|
||||
|
||||
Let’s see some more dock customization options in Ubuntu.
|
||||
|
||||
Imagine you added too many applications to the dock or have too many applications open. It will fill up the space and you’ll have to scroll to the top and bottom to go to the applications at end points.
|
||||
|
||||
What you can do here is to change the icon size and the dock will now accommodate more icons. Don’t make it too small, though.
|
||||
|
||||
![Normal Icon Size Dock][13]
|
||||
|
||||
![Smaller Icon Size Dock][14]
|
||||
|
||||
To do that, go to Settings-> Appearance and change it by moving the slider under Icon size. The default icons size is 48 pixels.
|
||||
|
||||
![Changing Icon Size In Ubuntu Dock][15]
|
||||
|
||||
#### Hide mounted disks from the launcher
|
||||
|
||||
If you plug in a USB disk or SD Card, it is mounted to the system, and an icon appear in the launcher immediately. This is helpful because you can right click on it and select safely remove drive option.
|
||||
|
||||
![External Mounted Disks In Ubuntu Dock][16]
|
||||
|
||||
If you somehow find it troublesome, you can turn this feature off. Don’t worry, you can still access the mounted drives from the file manager.
|
||||
|
||||
Open a terminal and use the following command:
|
||||
|
||||
```
|
||||
gsettings set org.gnome.shell.extensions.dash-to-dock show-mounts false
|
||||
```
|
||||
|
||||
The changes take into effect immediately. You won’t be bothered with mounted disk being displayed in the launcher.
|
||||
|
||||
If you want the default behavior back, use this command:
|
||||
|
||||
```
|
||||
gsettings set org.gnome.shell.extensions.dash-to-dock show-mounts true
|
||||
```
|
||||
|
||||
### Change the behavior of dock
|
||||
|
||||
Let’s customize the default behavior of the dock and make it more suitable to your needs.
|
||||
|
||||
#### Enable minimize on click
|
||||
|
||||
If you click on the icon of a running application, its window will be brought to focus. That’s fine. However, if you click on it, nothing happens. By default, clicking on the same icon won’t minimize the application.
|
||||
|
||||
Well, this is the behavior in modern desktop, but I don’t like it. I prefer that the application is minimized when I click on its icon for the second time.
|
||||
|
||||
If you are like me, you may want to [enable click to minimize option in Ubuntu][17]:
|
||||
|
||||
![A Video from YouTube][18]
|
||||
|
||||
To do that, open a terminal and enter the following command:
|
||||
|
||||
```
|
||||
gsettings set org.gnome.shell.extensions.dash-to-dock click-action 'minimize'
|
||||
```
|
||||
|
||||
#### Auto-hide Ubuntu dock and get more screen space
|
||||
|
||||
If you want to utilize the maximum screen space, you can enable auto-hide option for the dock in Ubuntu.
|
||||
|
||||
This will hide the dock, and you’ll get the entire screen. The dock is still accessible, though. Move your cursor to the location of the dock where it used to be, and it will appear again. When the dock reappears, it is overlaid on the running application window. And that’s a good thing otherwise too many elements would start moving on the screen.
|
||||
|
||||
The auto-hide option is available in Settings-> Appearance and under Dock section. Just toggle it.
|
||||
|
||||
![Autohide the Dock Ubuntu][19]
|
||||
|
||||
If you don’t like this behavior, you can enable it again the same way.
|
||||
|
||||
#### Disable Ubuntu dock
|
||||
|
||||
Auto-hide option is good enough for many people, but some users simply don’t like the dock. If you are one of those users, you also have the option to disable the Ubuntu dock entirely.
|
||||
|
||||
Starting with Ubuntu 20.04, you have the Extensions application available at your disposal to [manage GNOME Extensions][20].
|
||||
|
||||
![Gnome Extensions App Ubuntu][21]
|
||||
|
||||
With this Extensions application, you can easily disable or re-enable the dock.
|
||||
|
||||
![Disable Dock Ubuntu][22]
|
||||
|
||||
### Advanced dock customization with dconf-editor [Not recommended]
|
||||
|
||||
##### Warning
|
||||
|
||||
The dconf-editor allows you to change almost every aspect of the GNOME desktop environment. This is both good and bad because you must be careful in editing. Most of the settings can be changed on the fly, without asking for conformation. While you may reset the changes, you could still put your system in such a state that it would be difficult to put things back in order.For this reason, I advise not to play with dconf-editor, specially if you don’t like spending time in troubleshooting and fixing problems or if you are not too familiar with Linux and GNOME.
|
||||
|
||||
The [dconf editor][23] gives you additional options to customize the dock in Ubuntu. Install it from the software center and then navigate to org > gnome > shell > extensions > dash-to-dock. You’ll find plenty of options here. I cannot even list them all here.
|
||||
|
||||
![Dconf Editor Dock][24]
|
||||
|
||||
### Replace the dock in Ubuntu
|
||||
|
||||
There are several third-party dock applications available for Ubuntu and other Linux distributions. You can install a dock of your choice and use it.
|
||||
|
||||
For example, you can install Plank dock from the software center and use it in similar fashion to Ubuntu dock.
|
||||
|
||||
![Plank Dock Ubuntu][25]
|
||||
|
||||
Disabling Ubuntu Dock would be a better idea in this case. It won’t be wise to use multiple docks at the same time.
|
||||
|
||||
### Conclusion
|
||||
|
||||
This tutorial is about customizing the default dock or launcher provided in Ubuntu’s GNOME implementation. Some suggestions should work on the dock in vanilla GNOME as work well.
|
||||
|
||||
I have shown you most of the common Ubuntu dock customization. You don’t need to go and blindly follow all of them. Read and think which one suits your need and then act upon it.
|
||||
|
||||
Was it too trivial or did you learn something new? Would you like to see more such tutorials? I welcome your suggestions and feedback on dock customization.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/customize-ubuntu-dock/
|
||||
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lkxed][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/abhishek/
|
||||
[b]: https://github.com/lkxed
|
||||
[1]: https://itsfoss.com/wp-content/uploads/2021/01/ubuntu-dock.png
|
||||
[2]: https://player.vimeo.com/video/534830884
|
||||
[3]: https://www.youtube.com/c/itsfoss?sub_confirmation=1
|
||||
[4]: https://itsfoss.com/wp-content/uploads/2021/01/add-icons-to-dock.png
|
||||
[5]: https://itsfoss.com/wp-content/uploads/2021/01/remove-icons-from-dock.png
|
||||
[6]: https://itsfoss.com/wp-content/uploads/2021/01/reorder-icons-on-ubuntu-docks-800x430.gif
|
||||
[7]: https://itsfoss.com/wp-content/uploads/2021/01/right-click-icons-ubuntu-dock.png
|
||||
[8]: https://itsfoss.com/wp-content/uploads/2021/01/keyboard-shortcut-for-ubuntu-dock.png
|
||||
[9]: https://itsfoss.com/move-unity-launcher-bottom/
|
||||
[10]: https://itsfoss.com/wp-content/uploads/2021/01/change-launcher-position-ubuntu.png
|
||||
[11]: https://itsfoss.com/wp-content/uploads/2021/01/change-dock-position-ubuntu.png
|
||||
[12]: https://itsfoss.com/wp-content/uploads/2021/01/ubuntu-dock-settings-multimonitor.png
|
||||
[13]: https://itsfoss.com/wp-content/uploads/2021/01/normal-icon-size-dock.jpg
|
||||
[14]: https://itsfoss.com/wp-content/uploads/2021/01/smaller-icon-size-dock.jpg
|
||||
[15]: https://itsfoss.com/wp-content/uploads/2021/01/changing-icon-size-in-ubuntu-dock.png
|
||||
[16]: https://itsfoss.com/wp-content/uploads/2021/01/external-mounted-disks-in-ubuntu-dock.png
|
||||
[17]: https://itsfoss.com/click-to-minimize-ubuntu/
|
||||
[18]: https://giphy.com/embed/52FlrSIMxnZ1qq9koP
|
||||
[19]: https://itsfoss.com/wp-content/uploads/2021/01/autohide-dock-ubuntu.png
|
||||
[20]: https://itsfoss.com/gnome-shell-extensions/
|
||||
[21]: https://itsfoss.com/wp-content/uploads/2020/06/GNOME-extensions-app-ubuntu.jpg
|
||||
[22]: https://itsfoss.com/wp-content/uploads/2021/01/disable-dock-ubuntu.png
|
||||
[23]: https://wiki.gnome.org/Apps/DconfEditor
|
||||
[24]: https://itsfoss.com/wp-content/uploads/2021/01/dconf-editor-dock.png
|
||||
[25]: https://itsfoss.com/wp-content/uploads/2021/01/plank-dock-Ubuntu-800x382.jpg
|
Loading…
Reference in New Issue
Block a user